From: jenkins-bot Date: Thu, 28 Mar 2019 03:13:38 +0000 (+0000) Subject: Merge "Change name of main page in Sardinian (sc)" X-Git-Tag: 1.34.0-rc.0~2294 X-Git-Url: http://git.cyclocoop.org//%22http:/%22.attribut_html%28%24lesurls%5B%24numero%5D%29.%22/%22?a=commitdiff_plain;h=644dd792caa651a8279e786fc8fc2418eefc7811;hp=6e24330cd5591645cbee65baf226bbf522a70386;p=lhc%2Fweb%2Fwiklou.git Merge "Change name of main page in Sardinian (sc)" --- diff --git a/.gitignore b/.gitignore index def5a08eca..8cacb1ee30 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ sftp-config.json # Building & testing npm-debug.log node_modules/ +/resources/lib/.foreign /tests/phpunit/phpunit.phar /tests/selenium/log .eslintcache diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000000..e4ba47f265 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,197 @@ + '.phan/internal_stubs/memcached.phan_php', + 'oci8' => '.phan/internal_stubs/oci8.phan_php', + 'sqlsrv' => '.phan/internal_stubs/sqlsrv.phan_php', + 'tideways' => '.phan/internal_stubs/tideways.phan_php', +]; + +$cfg['directory_list'] = [ + 'includes/', + 'languages/', + 'maintenance/', + 'mw-config/', + 'resources/', + 'vendor/', + '.phan/stubs/', +]; + +$cfg['exclude_analysis_directory_list'] = [ + 'vendor/', + '.phan/stubs/', + // The referenced classes are not available in vendor, only when + // included from composer. + 'includes/composer/', + // Directly references classes that only exist in Translate extension + 'maintenance/language/', + // External class + 'includes/libs/jsminplus.php', +]; + +$cfg['suppress_issue_types'] = array_merge( $cfg['suppress_issue_types'], [ + // approximate error count: 18 + "PhanAccessMethodInternal", + // approximate error count: 17 + "PhanCommentParamOnEmptyParamList", + // approximate error count: 30 + "PhanCommentParamWithoutRealParam", + // approximate error count: 2 + "PhanCompatibleNegativeStringOffset", + // approximate error count: 1 + "PhanEmptyFQSENInCallable", + // approximate error count: 1 + "PhanInvalidCommentForDeclarationType", + // approximate error count: 6 + "PhanNonClassMethodCall", + // approximate error count: 21 + "PhanParamReqAfterOpt", + // approximate error count: 27 + "PhanParamSignatureMismatch", + // approximate error count: 4 + "PhanParamSignatureMismatchInternal", + // approximate error count: 1 + "PhanParamSignatureRealMismatchTooFewParameters", + // approximate error count: 1 + "PhanParamSuspiciousOrder", + // approximate error count: 127 + "PhanParamTooMany", + // approximate error count: 2 + "PhanParamTooManyCallable", + // approximate error count: 1 + "PhanParamTooManyInternal", + // approximate error count: 2 + "PhanPluginDuplicateExpressionBinaryOp", + // approximate error count: 2 + "PhanTraitParentReference", + // approximate error count: 27 + "PhanTypeArraySuspicious", + // approximate error count: 33 + "PhanTypeArraySuspiciousNullable", + // approximate error count: 26 + "PhanTypeComparisonFromArray", + // approximate error count: 2 + "PhanTypeComparisonToArray", + // approximate error count: 1 + "PhanTypeConversionFromArray", + // approximate error count: 2 + "PhanTypeExpectedObjectOrClassName", + // approximate error count: 7 + "PhanTypeExpectedObjectPropAccess", + // approximate error count: 3 + "PhanTypeInstantiateAbstract", + // approximate error count: 1 + "PhanTypeInvalidCallableArraySize", + // approximate error count: 62 + "PhanTypeInvalidDimOffset", + // approximate error count: 10 + "PhanTypeInvalidExpressionArrayDestructuring", + // approximate error count: 1 + "PhanTypeInvalidLeftOperand", + // approximate error count: 7 + "PhanTypeInvalidLeftOperandOfIntegerOp", + // approximate error count: 2 + "PhanTypeInvalidRightOperand", + // approximate error count: 2 + "PhanTypeInvalidRightOperandOfIntegerOp", + // approximate error count: 1 + "PhanTypeMagicVoidWithReturn", + // approximate error count: 152 + "PhanTypeMismatchArgument", + // approximate error count: 28 + "PhanTypeMismatchArgumentInternal", + // approximate error count: 1 + "PhanTypeMismatchBitwiseBinaryOperands", + // approximate error count: 1 + "PhanTypeMismatchDeclaredParam", + // approximate error count: 2 + "PhanTypeMismatchDimEmpty", + // approximate error count: 29 + "PhanTypeMismatchDimFetch", + // approximate error count: 10 + "PhanTypeMismatchForeach", + // approximate error count: 77 + "PhanTypeMismatchProperty", + // approximate error count: 88 + "PhanTypeMismatchReturn", + // approximate error count: 43 + "PhanTypeMissingReturn", + // approximate error count: 1 + "PhanTypeNoAccessiblePropertiesForeach", + // approximate error count: 4 + "PhanTypeNonVarPassByRef", + // approximate error count: 12 + "PhanTypeObjectUnsetDeclaredProperty", + // approximate error count: 9 + "PhanTypeSuspiciousNonTraversableForeach", + // approximate error count: 3 + "PhanTypeSuspiciousStringExpression", + // approximate error count: 22 + "PhanUndeclaredConstant", + // approximate error count: 3 + "PhanUndeclaredInvokeInCallable", + // approximate error count: 242 + "PhanUndeclaredMethod", + // approximate error count: 847 + "PhanUndeclaredProperty", + // approximate error count: 1 + "PhanUndeclaredTypeReturnType", + // approximate error count: 3 + "PhanUndeclaredTypeThrowsType", + // approximate error count: 2 + "PhanUndeclaredVariableAssignOp", + // approximate error count: 55 + "PhanUndeclaredVariableDim", + // approximate error count: 4 + "PhanUnextractableAnnotationElementName", + // approximate error count: 4 + "PhanUnextractableAnnotationSuffix", +] ); + +$cfg['ignore_undeclared_variables_in_global_scope'] = true; +$cfg['globals_type_map']['IP'] = 'string'; + +return $cfg; diff --git a/.phan/internal_stubs/memcached.phan_php b/.phan/internal_stubs/memcached.phan_php new file mode 100644 index 0000000000..8a85bafe39 --- /dev/null +++ b/.phan/internal_stubs/memcached.phan_php @@ -0,0 +1,180 @@ + */includes/Feed\.php - */includes/RevisionList\.php */includes/installer/PhpBugTests\.php */includes/specials/SpecialMostinterwikis\.php */includes/compat/XMPReader\.php @@ -229,10 +228,8 @@ */includes/parser/Preprocessor_Hash\.php */includes/parser/Preprocessor\.php */includes/PathRouter\.php - */includes/poolcounter/PoolCounter\.php */includes/PrefixSearch\.php */includes/profiler/SectionProfiler\.php - */includes/RevisionList\.php */includes/search/SearchEngine\.php */includes/specialpage/LoginSignupSpecialPage\.php */includes/specials/forms/PreferencesFormLegacy\.php diff --git a/HISTORY b/HISTORY index a9260699fb..7895316cd2 100644 --- a/HISTORY +++ b/HISTORY @@ -2,6 +2,25 @@ Change notes from older releases. For current info see RELEASE-NOTES-1.33. = MediaWiki 1.32 = +== MediaWiki 1.32.1 == + +=== Changes since MediaWiki 1.32.0 === +* (T213577) rdbms: avoid transaction status errors from ping() in rollback(). +* rdbms: Pass required parameter. +* rdbms: do not treat SAVEPOINT and RELEASE SAVEPOINT as write queries. +* (T204531) rdbms: reduce LoadBalancer replication log spam. +* (T213489) Avoid session double-start in Setup.php. +* (T213717) Correct namespace 'Template' for gom-deva +* (T198054) Fix login page crash caused by unknown language via ?uselang +* (T215324) (T210937) list=users mistakenly reports user as missing. +* (T209483) Add ILBFactory::redefineLocalDomain method. This is intended for +use with scripts like addWiki.php to avoid mismatched domain errors. +* (T208871) The hard-coded Google search form on the database error page was +removed. +* (T204800) Fix Title::getFragmentForURL for bad interwiki prefix +* (T215566) Fix installer being unable to determine if the database exists +during a fresh installation. + == MediaWiki 1.32.0 == === Changes since MediaWiki 1.32.0-rc.2 === @@ -4785,6 +4804,11 @@ of files that are no longer available follows. = MediaWiki 1.23 = +== MediaWiki 1.23.17 == + +=== Changes since 1.23.16 === +* Fix syntax errors introduced in 1.23.16 when running PHP 5.3. + == MediaWiki 1.23.16 == This is a security and maintenance release of the MediaWiki 1.23 branch. @@ -7044,6 +7068,52 @@ changes to languages because of Bugzilla reports. == MediaWiki 1.19 == +== MediaWiki 1.19.24 == + +This is a security and maintenance release of the MediaWiki 1.19 branch. + +=== Changes since 1.19.23 === + +* ({{bug|T85848}}, {{bug|T71210}}) SECURITY: Don't parse XMP blocks that +contain XML entities, to prevent various DoS attacks. +* ({{bug|T88310}}) SECURITY: Always expand xml entities when checking SVG's. +* ({{bug|T73394}}) SECURITY: Escape > in Html::expandAttributes to prevent XSS. +* ({{bug|T85855}}) SECURITY: Don't execute another user's CSS or JS on preview. +* ({{bug|T85349}}, {{bug|T85850}}, {{bug|T86711}}) SECURITY: Multiple issues +fixed in SVG filtering to prevent XSS and protect viewer's privacy. + +== MediaWiki 1.19.23 == + +This is a security and maintenance release of the MediaWiki 1.19 branch. + +=== Changes since 1.19.22 === + +* (bug T76686) [SECURITY] thumb.php outputs wikitext message as raw HTML, which +could lead to xss. Permission to edit MediaWiki namespace is required to +exploit this. +* (bug T74222) The original patch for T74222 was reverted as unnecessary. +* Add missing $ in front of variable in OutputPage.php + +== MediaWiki 1.19.22 == + +This is a security and maintenance release of the MediaWiki 1.19 branch. + +=== Changes since 1.19.21 === + +* ({{bug|66776}}, {{bug|71478}}) SECURITY: User PleaseStand reported a way to +inject code into API clients that used format=php to process pages that +underwent flash policy mangling. This was fixed along with improving how the +mangling was done for format=json, and allowing sites to disable the mangling +using $wgMangleFlashPolicy. +* ({{bug|72222}}) SECURITY: Do not show log action when the entry is revdeleted +with DELETED_ACTION. NOTICE: this may be reverted in a future release pending a +public RFC about the desired functionality. This issue was reported by user +Bawolff. +* ({{bug|71621}}) Make allowing site-wide styles on restricted special pages a +config option. +* $wgMangleFlashPolicy was added to make MediaWiki's mangling of anything that +might be a flash policy directive configurable. + == MediaWiki 1.19.21 == This is a maintenance release of the MediaWiki 1.19 branch. @@ -7618,6 +7688,20 @@ changes to languages because of Bugzilla reports. == MediaWiki 1.18 == +== MediaWiki 1.18.6 == +2012-11-29 + +This is a maintenance and security release of the MediaWiki 1.18 branch + +=== Changes since 1.18.5 === +* ([[bugzilla:40995|bug 40995]]) Prevent session fixation in Special:UserLogin +(CVE-2012-5391) +* ([[bugzilla:41400|bug 41400]]) Prevent linker regex from exceeding PCRE +backtrack limit +* Localisation updates +* Increase permitted runtime for testParserTest +* ([[bugzilla:36179|bug 36179]]) Unquote 'null' for PostgreSQL. + == MediaWiki 1.18.5 == 2012-08-30 @@ -11341,6 +11425,43 @@ regularly. Below only new and removed languages are listed. == MediaWiki 1.13 == +== MediaWiki 1.13.5 == + +February 22, 2009 + +This is a maintenance update to the Summer 2008 snapshot release of MediaWiki. + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept +"ready to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature developments +will be made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain +it from source control: http://www.mediawiki.org/wiki/Download_from_SVN + +== Changes since 1.13.4 == + +* (bug 17449) Fixed PostgreSQL installation +* (bug 17527) Fixed missing MySQL-specific options in installer + +== Changes since 1.13.3 == + +A number of cross-site scripting (XSS) security vulnerabilities were discovered +in the web-based installer (config/index.php). These vulnerabilities all +require a live installer -- once the installer has been used to install a wiki, +it is deactivated. + +Note that cross-site scripting vulnerabilities can be used to attack any website +in the same cookie domain. So if you have an uninstalled copy of MediaWiki on +the same site as an active web service, MediaWiki could be used to attack the +active service. + +If you are hosting an old copy of MediaWiki that you have never installed, you +are advised to remove it from the web. + == Changes since 1.13.2 == David Remahl of Apple's Product Security team has identified a number of @@ -11983,9 +12104,143 @@ Other changes in this release: the page * list=exturlusage in "list all links" mode can now filter by protocol +== MediaWiki 1.12 == +== MediaWiki 1.12.4 == -== MediaWiki 1.12 == +February 7, 2009 + +A number of cross-site scripting (XSS) security vulnerabilities were discovered +in the web-based installer (config/index.php). These vulnerabilities all +require a live installer -- once the installer has been used to install a wiki, +it is deactivated. + +Note that cross-site scripting vulnerabilities can be used to attack any +website in the same cookie domain. So if you have an uninstalled copy of +MediaWiki on the same site as an active web service, MediaWiki could be used to +attack the active service. + +If you are hosting an old copy of MediaWiki that you have never installed, you +are advised to remove it from the web. + +== MediaWiki 1.12.3 == + +* Fixed packaging/distribution error. Many files were missing from the +distributed tarball. + +== MediaWiki 1.12.2 == + +David Remahl of Apple's Product Security team has identified a number of +security issues in previous releases of MediaWiki. Subsequent analysis by the +MediaWiki development team expanded the scope of these vulnerabilities. The +issues with a significant impact are as follows: + +* A local script injection vulnerability affecting Internet Explorer clients +for all MediaWiki installations with uploads enabled. [CVE-2008-5250] +* A local script injection vulnerability affecting clients with SVG scripting +capability (such as Firefox 1.5+), for all MediaWiki installations with SVG +uploads enabled. [CVE-2008-5250] +* A CSRF vulnerability affecting the Special:Import feature, for all MediaWiki +installations since the feature was introduced in 1.3.0. [CVE-2008-5252] + +A local script injection vulnerability allows an attacker with a wiki account +to steal another user's login session, and to act as that user on the wiki. The +attacker uploads a malicious script file, and tricks the victim into executing +it. + +CSRF vulnerabilities allow an attacker to act as an authorised user on the +wiki, but unlike an XSS vulnerability, the attacker can only act as the user in +a specific and restricted way. The present CSRF vulnerability allows pages to +be edited, with forged revision histories. Like an XSS vulnerability, the +authorised user must visit the malicious web page to activate the attack. + +These three vulnerabilities are all fixed in this release. + +David Remahl also reminded us of some security-related configuration issues: + +* By default, MediaWiki stores a backup of deleted images in the images/deleted +directory. If you do not want these images to be publically accessible, make +sure this directory is not accessible from the web. MediaWiki takes some steps +to avoid leaking these images, but these measures are not perfect. +* Set display_errors=off in your php.ini to avoid path disclosure via PHP fatal +errors. This is the default on most shared web hosts. +* Enabling MediaWiki's debugging features, such as $wgShowExceptionDetails, may +lead to path disclosure. + +Other changes in this release: + +* Avoid fatal error in profileinfo.php when not configured. +* Add a .htaccess to deleted images directory for additional protection against +exposure of deleted files with known SHA-1 hashes on default installations. +* Avoid streaming uploaded files to the user via index.php. This allows +security-conscious users to serve uploaded files via a different domain, and +thus client-side scripts executed from that domain cannot access the login +cookies. Affects Special:Undelete, img_auth.php and thumb.php. +* When streaming files via index.php, use the MIME type detected from the file +extension, not from the data. This reduces the XSS attack surface. +* Blacklist redirects via Special:Filepath. Such redirects exacerbate any XSS +vulnerabilities involving uploads of files containing scripts. +* Internationalisation updates. + +== MediaWiki 1.12.1 == + +Changes since 1.12.0: +* (bug [[bugzilla:13522|13522]]) Fix fatal error in Parser::extractTagsAndParams +* (bug [[bugzilla:12077|12077]]) Fix HTML nesting for TOC +* (bug [[bugzilla:13532|13532]]) Use proper timestamp call when reverting images +* (bug [[bugzilla:13649|13649]], [[bugzilla:14084|14084]]) Bad call to +wfTimestamp() +* (bug [[bugzilla:13770|13770]]) Use Preprocessor_Hash by default to avoid +missing DOM module errors +* (bug [[bugzilla:13442|13442]]) API: Missing pages in prop=langlinks and +prop=extlinks are now handled properly. +* (bug [[bugzilla:13482|13482]]) API: Disabled search types handled properly +* (bug [[bugzilla:13836|13836]]) API: Fixed fatal errors resulting from +combining iiprop=metadata with format=xml +* (bug [[bugzilla:11633|11633]]) API: Explicitly convert redirect titles to +strings due to PHP's very weak typing on array keys. +* API: Fixing main page display in meta=siteinfo +* (bug [[bugzilla:11719|11719]]) API: Remove trailing blanks in YAML output. +* (bug [[bugzilla:13718|13718]]) API: Return the proper continue parameter for +cmsort=timestamp +* Security: Work around misconfiguration by requiring strict comparisons for +in_array in User::isAllowed(). +* Security: Fixed XSS vulnerability in useskin parameter. + +== MediaWiki 1.12.0 == + +This is the quarterly branch release of [[MediaWiki]] for Winter 2008. + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept "ready +to run", and in fact runs our own sites on [[wikipedia:|Wikipedia]]. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature developments will be +made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain it +from source control: [[Download from SVN]]. + +Changes since 1.12.0rc1: +*(bug [[bugzilla:13359|13359]]) Double-escaping in [[Special:Allpages]]. +*Localization updates. + +== MediaWiki 1.12.0rc1 == + +This is a release candidate of the Winter 2008 quarterly snapshot release of +[[MediaWiki]]. + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept "ready +to run", and in fact runs our own sites on [[wikipedia:|Wikipedia]]. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature developments will be +made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain it +from source control: [[Download from SVN]]. This is the Winter 2007 quarterly release. @@ -12539,6 +12794,76 @@ Full API documentation is available at https://www.mediawiki.org/wiki/API == MediaWiki 1.11 == +== MediaWiki 1.11.2 == + +March 2, 2008 + +This is a security release of the Fall 2007 snapshot release of MediaWiki. +Possible cross-site information leaks using the callback parameter for +JSON-formatted results in the API are prevented by dropping user credentials. + +MediaWiki release versions prior to 1.11 are not vulnerable, as they do not +include the callback feature which allows client-side JavaScript on other sites +to reach API data. + +Changes in this release: + +* User credentials are dropped for API JSON requests using a callback +* Edit tokens are not reported for API JSON requests using a callback + +== MediaWiki 1.11.1 == + +January 23, 2008 + +This is a security and bugfix release of the Fall 2007 snapshot release of + MediaWiki. A potential XSS injection vector affecting api.php only for + Microsoft Internet Explorer users has been closed. + +Changes in this release: +* (bug [[bugzilla:11450|11450]]) Fix creation of objectcache table on upgrade +* (bug [[bugzilla:11462|11462]]) Fix typo in LanguageGetSpecialPageAliases hook +name +* Fix regression in LinkBatch.php breaking PHP 5.0 +* Security fix for API on MSIE + +To work around the vulnerability without upgrading, you may disable the API if +you don't need it: +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +Not vulnerable versions: +* 1.12 or later +* 1.11 >= 1.11.1 +* 1.10 >= 1.10.3 +* 1.9 >= 1.9.5 +* 1.8 any version (if $wgEnableAPI has been left off) + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.2 +* 1.9 <= 1.9.4 +* 1.8 any version (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the API +functionality, however the BotQuery extension is similarly vulnerable unless +updated to the latest SVN version. + +== MediaWiki 1.11.0 == + +September 10, 2007 + +This is the Fall 2007 snapshot release of MediaWiki. + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept "ready +to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature developments will be +made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain it +from source control: [[Download from SVN]] + This is the Summer 2007 branch release of MediaWiki. MediaWiki is now using a "continuous integration" development model with @@ -12552,6 +12877,33 @@ will be made on the development trunk and appear in the next quarterly release. Those wishing to use the latest code instead of a branch release can obtain it from source control: https://www.mediawiki.org/wiki/Download_from_SVN +== Changes since 1.11.0rc1 == + +A possible HTML/XSS injection vector in the API pretty-printing mode has been +found and fixed. + +The vulnerability may be worked around in an unfixed version by simply +disabling the API interface if it is not in use, by adding this to +[[Manual:LocalSettings.php|LocalSettings.php]]:
+[[Manual:$wgEnableAPI|$wgEnableAPI]] = false;
+(This is the default setting in 1.8.x.) + +Not vulnerable versions: +* 1.11 >= 1.11.0 +* 1.10 >= 1.10.2 +* 1.9 >= 1.9.4 +* 1.8 >= 1.8.5 + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.1 +* 1.9 <= 1.9.3 +* 1.8 <= 1.8.4 (if [[Manual:$wgEnableAPI|$wgEnableAPI]] has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the faulty +function, however the [[Extension:BotQuery|BotQuery extension]] is similarly +vulnerable unless updated to the latest SVN version. + == Configuration changes since 1.10 == * $wgThumbUpright - Adjust width of upright images when parameter 'upright' is @@ -12560,7 +12912,8 @@ it from source control: https://www.mediawiki.org/wiki/Download_from_SVN usergroups * $wgEnotifImpersonal, $wgEnotifUseJobQ - Bulk mail options for large sites * $wgShowHostnames - Expose server host names through the API and HTML comments -* $wgSaveDeletedFiles has been removed, the feature is now enabled unconditionally +* $wgSaveDeletedFiles has been removed, the feature is now enabled +unconditionally == New features since 1.10 == @@ -13127,6 +13480,121 @@ Full API documentation is available at https://www.mediawiki.org/wiki/API == MediaWiki 1.10 == +== MediaWiki 1.10.4 == + +March 2, 2008 + +* Correction for API path fix, broken in 1.10.3 + +== MediaWiki 1.10.3 == + +January 23, 2008 + +This is a security update to the Winter 2007 quarterly release. A potential +XSS injection vector affecting api.php only for Microsoft Internet Explorer +users has been closed. + + +To work around the vulnerability without upgrading, you may disable the API if +you don't need it: + +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +Not vulnerable versions: +* 1.12 or later +* 1.11 >= 1.11.1 +* 1.10 >= 1.10.3 +* 1.9 >= 1.9.5 +* 1.8 any version (if $wgEnableAPI has been left off) + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.2 +* 1.9 <= 1.9.4 +* 1.8 any version (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the API +functionality, however the BotQuery extension is similarly vulnerable unless +updated to the latest SVN version. + +== MediaWiki 1.10.2 == +September 10, 2007 + +This is a security fix update to the Spring 2007 quarterly release snapshot. A +possible HTML/XSS injection vector in the API pretty-printing mode has been +found and fixed. + +The vulnerability may be worked around in an unfixed version by simply +disabling the API interface if it is not in use, by adding this to +LocalSettings.php: +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +Not vulnerable versions: +* 1.11 >= 1.11.0 +* 1.10 >= 1.10.2 +* 1.9 >= 1.9.4 +* 1.8 >= 1.8.5 + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.1 +* 1.9 <= 1.9.3 +* 1.8 <= 1.8.4 (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the faulty +function, however the BotQuery extension is similarly vulnerable unless updated +to the latest SVN version. + +== MediaWiki 1.10.1 == +July 13, 2007 + +This is a bugfix update to the Spring 2007 quarterly release snapshot. A number +of fixes to improve compatibility with PostgreSQL, some versions of MySQL, and +some PHP configurations are included. + +Changes since 1.10.0: + +* (bug [[bugzilla:9417|9417]]) Uploading new versions of images when using +Postgres no longer throws warnings. +* (bug [[bugzilla:9908|9908]]) Using tsearch2 with Postgres 8.1 no longer gives +an error. +* (bug [[bugzilla:9973|9973]]) Changed size was shown in advanced recentchanges +collapsible items with $wgRCShowChangedSized = false. +* Fixed installation on MyISAM or old InnoDB with charset=utf8, was giving +overlong key errors. +* Fixed zero-padding issues with MySQL 5 binary schema +* (bug [[bugzilla:9820|9820]]) session.save_path check no longer halts +installation, but warns of possible bad values +* (bug [[bugzilla:9978|9978]]) Fixed session.save_path validation when using +extended configuration format, e.g. "5;/tmp" + +== MediaWiki 1.10.0 == +May 9, 2007 + +This is the quarterly release snapshot for Spring 2007. See below for a full +list of changes since the 1.9.x series. + +Changes since 1.10.0rc2: + +* (bug [[bugzilla:9808|9808]]) Fix regression that ignored user 'rclimit' +option for Special:Contributions + +== MediaWiki 1.10.0rc2 == +May 4, 2007 + +THIS IS A RELEASE CANDIDATE MADE AVAILABLE FOR TESTING! +A FINAL 1.10.0 RELEASE WILL APPEAR WITHIN A FEW DAYS. + +Changes since 1.10.0rc1: +* Various l10n fixes and updates +* Fix for upgrade of page_restrictions table +* (bug [[bugzilla:9780|9780]]) Fix normalization of titles with initial colon +followed by whitespace +* Fix for regression in upload: wrong size info saved into image table +* Avoid cyclic stub problems when authorization hooks do funny things with the +user and the database at load time + +== MediaWiki 1.10.0rc1 == This is the Spring 2007 branch release of MediaWiki. MediaWiki is now using a "continuous integration" development model with @@ -13616,10 +14084,159 @@ break. Don't forget to always back up your database before upgrading! See the file UPGRADE for more detailed upgrade instructions. = MediaWiki release notes = - Security reminder: MediaWiki does not require PHP's register_globals setting since version 1.2.0. If you have it on, turn it *off* if you can. += MediaWiki 1.9 = + +== MediaWiki 1.9.6 == + +March 2, 2008 + +* Correction for API path fix, broken in 1.9.5 + +== MediaWiki 1.9.5 == + +January 23, 2008 + +This is a security update to the Winter 2007 quarterly release. A potential XSS +injection vector affecting api.php only for Microsoft Internet Explorer users +has been closed. + + +To work around the vulnerability without upgrading, you may disable the API if +you don't need it: + +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +Not vulnerable versions: +* 1.12 or later +* 1.11 >= 1.11.1 +* 1.10 >= 1.10.3 +* 1.9 >= 1.9.5 +* 1.8 any version (if $wgEnableAPI has been left off) + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.2 +* 1.9 <= 1.9.4 +* 1.8 any version (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the API +functionality, however the BotQuery extension is similarly vulnerable unless +updated to the latest SVN version. + +== MediaWiki 1.9.4 == + +September 10, 2007 + +This is a security and bug fix update to the Winter 2007 quarterly release. +Minor compatibility fixes for IIS 5 are included. + +* (bug [[bugzilla:8847|8847]]) Strip spurious #fragments from request URI to +fix redirect loops on some server configurations +* A possible HTML/XSS injection vector in the API pretty-printing mode has been +found and fixed. + +The vulnerability may be worked around in an unfixed version by simply +disabling the API interface if it is not in use, by adding this to +LocalSettings.php: + +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +Not vulnerable versions: +* 1.11 >= 1.11.0 +* 1.10 >= 1.10.2 +* 1.9 >= 1.9.4 +* 1.8 >= 1.8.5 + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.1 +* 1.9 <= 1.9.3 +* 1.8 <= 1.8.4 (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the faulty +function, however the BotQuery extension is similarly vulnerable unless updated +to the latest SVN version. + +== MediaWiki 1.9.3 == + +February 20, 2007 + +This is a security and bug-fix update to the Winter 2007 quarterly release. +Minor compatibility fixes for IIS and PostgreSQL are included. + +An XSS injection vulnerability based on Microsoft Internet Explorer's UTF-7 +charset autodetection was located in the AJAX support module, affecting MSIE +users on MediaWiki 1.6.x and up when the optional setting $wgUseAjax is enabled. + +If you are using an extension based on the optional Ajax module, either disable +it or upgrade to a version containing the fix: + +* 1.9: fixed in 1.9.3 +* 1.8: fixed in 1.8.4 +* 1.7: fixed in 1.7.3 +* 1.6: fixed in 1.6.10 + +There is no known danger in the default configuration, with ''$wgUseAjax'' off. + +* ([[mediazilla:8992|8992]]) Fix a remaining raw use of REQUEST_URI in history +* ([[mediazilla:8984|8984]]) Fix a database error in +Special:Recentchangeslinked when using the PostgreSQL database. +* Add ''charset'' to Content-Type headers on various HTTP error responses to +forestall additional UTF-7-autodetect XSS issues. PHP sends only ''text/html'' +by default when the script didn't specify more details, which some +inconsiderate browsers consider a license to autodetect the deadly, +hard-to-escape UTF-7. This fixes an issue with the Ajax interface error message +on MSIE when ''$wgUseAjax'' is enabled (not default configuration); this UTF-7 +variant on a previously fixed attack vector was discovered by Moshe BA from +BugSec: [http://www.bugsec.com/articles.php?Security=24 +http://www.bugsec.com/articles.php?Security=24] +* Trackback responses now specify XML content type + +== MediaWiki 1.9.2 == + +February 4, 2007 + +This is a bug-fix update that fixes some installation and other minor issues +with the 1.9.1 release as well as a security issue which was introduced in the +1.9 branch. + +JavaScript code which regenerated the "sortable tables" feature did not +properly sanitize input, leading to an HTML injection vulnerability. + +* ([[mediazilla:8774|8774]]) Fix path for GNU FDL rights icon on new installs +* ([[mediazilla:8819|8819]]) Fix full path disclosure with skins dependencies +* ([[mediazilla:8819|8819]]) Fixed data-loss bug in compressOld batch text +compression affecting pages which had null edits (move, protect, etc) as second +edit in a batch group. Isolated and patched by Travis Derouin. +* Security fix for sortable tables JavaScript + +== MediaWiki 1.9.1 == + +January 24, 2007 + +This is a bug-fix update that fixes some installation and upgrade issues with +the original 1.9.0 release. + +* ([[mediazilla:3000|3000]]) Fall back to SCRIPT_NAME plus QUERY_STRING when +REQUEST_URI is not available, as on IIS with PHP-CGI +* Security fix for DjVu images. (Only affects servers where .djvu file uploads +are enabled and ''$wgDjvuToXML'' is set.) +* ([[mediazilla:8638|8638]]) Fix update from 1.4 and earlier +* ([[mediazilla:8641|8641]]) Fix order of updates to ipblocks table for updates +from <=1.7 +* ([[mediazilla:8673|8673]]) Minor fix for web service API content-type header +* Fix API revision list on PHP 5.2.1; bad reference assignment +* Fixed up the AjaxSearch +* Exclude settings files when generating documentation. That could expose the +database user and password to remote users. +* ar: fix the 'create a new page' on search page when no exact match found +* Correct tooltip accesskey hint for Opera on the Macintosh (uses Shift-Esc-, +not Ctrl-). +* ([[mediazilla:8719|8719]]) Firefox release notes lie! Fix tooltips for +Firefox 2 on x11; accesskeys default settings appear to be same as Windows. == Changes since 1.8 == @@ -14310,6 +14927,154 @@ MediaWiki's parser test suite can now be expanded with additional test files. Custom extensions can add their test files to this array, and they will be run along with the main tests by maintenance/parserTests.php += MediaWiki 1.8= + +== MediaWiki 1.8.5 == + +September 10, 2007 + +This is a security fix update to the Fall 2006 quarterly release snapshot. A +possible HTML/XSS injection vector in the API pretty-printing mode has been +found and fixed. + +The vulnerability may be worked around in an unfixed version by simply +disabling the API interface if it is not in use, by adding this to +LocalSettings.php: + +:[[Manual:$wgEnableAPI|$wgEnableAPI]] = false; + +(This is the default setting in 1.8.x.) + +Not vulnerable versions: +* 1.11 >= 1.11.0 +* 1.10 >= 1.10.2 +* 1.9 >= 1.9.4 +* 1.8 >= 1.8.5 + +Vulnerable versions: +* 1.11 <= 1.11.0rc1 +* 1.10 <= 1.10.1 +* 1.9 <= 1.9.3 +* 1.8 <= 1.8.4 (if $wgEnableAPI has been switched on) + +MediaWiki 1.7 and below are not affected as they do not include the faulty +function, however the BotQuery extension is similarly vulnerable unless updated +to the latest SVN version. + +== MediaWiki 1.8.4 == + +February 20, 2007 + +This is a security and bug-fix update to the Fall 2006 quarterly release. + +An XSS injection vulnerability based on Microsoft Internet Explorer's UTF-7 +charset autodetection was located in the AJAX support module, affecting MSIE +users on MediaWiki 1.6.x and up when the optional setting +[[Manual:$wgUseAjax|$wgUseAjax]] is enabled. + +If you are using an extension based on the optional Ajax module, either disable +it or upgrade to a version containing the fix: +* 1.9: fixed in 1.9.3 +* 1.8: fixed in 1.8.4 +* 1.7: fixed in 1.7.3 +* 1.6: fixed in 1.6.10 + +There is no known danger in the default configuration, with $wgUseAjax off. + +* (bug [[bugzilla:8819|8819]]) Fix full path disclosure with skins dependencies +* Add 'charset' to Content-Type headers on various HTTP error responses to +forestall additional UTF-7-autodetect XSS issues. PHP sends only 'text/html' by +default when the script didn't specify more details, which some inconsiderate +browsers consider a license to autodetect the deadly, hard-to-escape UTF-7. +This fixes an issue with the Ajax interface error message on MSIE when +[[Manual:$wgUseAjax|$wgUseAjax]] is enabled (not default configuration); this +UTF-7 variant on a previously fixed attack vector was discovered by Moshe BA +from BugSec: http://www.bugsec.com/articles.php?Security=24 +* Trackback responses now specify XML content type + +== MediaWiki 1.8.3 == + +January 9, 2007 + +MediaWiki 1.8.3 fixes several issues in the Fall 2006 snapshot release: + +* ([[mediazilla:7831|7831]]) Regression in AutoAuthenticate hook +* Run PHP install version checks on update.php so command-line updaters see new +version requirements +* Do a check for the PHP 5.0.x 64-bit bug, since this is much more disruptive +as of MW 1.8 than it used to be. Install or upgrade now aborts with a warning +and a request to upgrade. +* XSS fix in AJAX module + +An XSS injection vulnerability was located in the AJAX support module, +affecting MediaWiki 1.6.x and up when the optional setting $wgUseAjax is +enabled. + +There is no danger in the default configuration, with $wgUseAjax off. + +If you are using an extension based on the optional AJAX module, either disable +it or upgrade to a version containing the fix: + +== MediaWiki 1.8.2 == + +October 13, 2006 + +MediaWiki 1.8.2 fixes several issues in the Fall 2006 snapshot release: + +* ([[mediazilla:7565|7565]]) Fixed typos in German localisation +* ([[mediazilla:7562|7562]]) Fix non-ASCII namespaces on Windows/XAMPP servers + +== MediaWiki 1.8.1 == + +October 11, 2006 + +MediaWiki 1.8.1 fixes several issues in the Fall 2006 snapshot release: + +* Fix PHP notice and estimates for dumpBackup.php and friends +* Improved register_globals paranoia checks +* ([[mediazilla:7545|7545]]) Fix PHP version check on install +* Experimental web API disabled by default +* Disable PHP exception backtrace printing unless $wgShowExceptionDetails is +set. Backtraces may contain sensitive information in function call parameters. + +== MediaWiki 1.8.0 == + +October 10, 2006 + +This is the quarterly release snapshot for Fall 2006. While the code has been +running on Wikipedia for some time, installation and upgrade bits may be less +well tested. Bug fix releases may follow in the coming days or weeks. + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept "ready +to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature development happen +will be made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain it +from source control: [[Download from SVN]] + +== Configuration changes == +* $wgUseETag, to enable/disable sending of HTTP ETag headers (default: disabled) +* $wgLegalTitleChars now includes '+' by default for better compatibility with +importing data dumps from Wikipedia +* $wgDefaultUserOptions now includes all default option settings instead of +only overrides. + +== Major new features == +* ([[mediazilla:7098|7098]]) Add an option to disable/enable sending of HTTP +ETag headers, as it seems to result in broken behaviour in combination with +Squid 2.6 (disabled by default). +* ([[mediazilla:550|550]]) Allow blocks on anonymous users only. +* ([[mediazilla:6420|6420]]) Render thumbnails for DJVU images, support +multipage DJVU display on image pages. Added new 'page=' thumbnail option to +select a page from a multipage djvu for thumbnail generation. +* Full Postgres support is now enabled. It requires version 8.1 or better, and +needs to have both plpgsql and tsearch2 already installed. +* ([[mediazilla:6386|6386]]) fix grammatical errors in danish naming of talk +namespaces. == Changes since 1.7 == @@ -14560,6 +15325,172 @@ they will be run along with the main tests by maintenance/parserTests.php * (bug 7537) Add php5 to $wgFileBlacklist * (bug 6929) Restore AutoAuthenticate hook +== Languages updated == +* Albanian (sq) +* Bashkir (ba) +* Bavarian (bar) stub file +* Belarusian (be) +* Bishnupriya (bpy) stub file +* Brazilian Portuguese (pt-br) +* Cantonese (zh-yue) +* Catalan (ca) +* Czech (cs) +* Dutch (nl) +* English (en) +* Finnish (fi) +* French (fr) +* Georgian (ka) +* German (de) +* Hebrew (he) +* Hungarian (hu) +* Indonesian (id) +* Japanese (ja) +* Korean (ko) +* Latin (la) +* Lojban (jbo) +* Macedonian (mk) +* Mazandarani (mzn) +* Polish (pl) +* Portuguese (pt) +* Ripuarian (ksh) +* Romani (rmy) +* Russian (ru) +* Slovak (sk) +* Spanish (es) +* Tajic (tg) +* Tatar (tt) +* Telugu (te) +* Uzbek (uz) +* Yiddish (yi) + +== Compatibility == +MediaWiki 1.8 requires PHP 5 (5.1 recommended). PHP 4 is no longer supported. + +MySQL 3.23.x is no longer supported; some older hosts may need to upgrade. At +this time we still recommend 4.0, but 4.1/5.0 will work fine in most cases. + +== Upgrading == +Some minor database changes have been made since 1.7: +* new fields and indexes on ipblocks +* index change on recentchanges + +Several changes from 1.5 and 1.6 do require updates to be run on upgrade. To +ensure that these tables are filled with data, run refreshLinks.php after the +upgrade. + +If you are upgrading from MediaWiki 1.4.x or earlier, some major database +changes are made, and there is a slightly higher chance that things could +break. Don't forget to always back up your database before upgrading! + +=== Caveats === +Some output, particularly involving user-supplied inline HTML, may not produce +100% valid or well-formed XHTML output. Testers are welcome to set $wgMimeType += "application/xhtml+xml"; to test for remaining problem cases, but this is not +recommended on live sites. (This must be set for MathML to display properly in +Mozilla.) + += MediaWiki 1.7= + +== MediaWiki 1.7.3 == + +February 20, 2007 + +This is a security and bug-fix update to the Summer 2006 quarterly release. + +An XSS injection vulnerability based on Microsoft Internet Explorer's UTF-7 +charset autodetection was located in the AJAX support module, affecting MSIE +users on MediaWiki 1.6.x and up when the optional setting +[[Manual:$wgUseAjax|$wgUseAjax]] is enabled. + +If you are using an extension based on the optional Ajax module, either disable +it or upgrade to a version containing the fix: + +* 1.9: fixed in 1.9.3 +* 1.8: fixed in 1.8.4 +* 1.7: fixed in 1.7.3 +* 1.6: fixed in 1.6.10 + +There is no known danger in the default configuration, with +[[Manual:$wgUseAjax|$wgUseAjax]] off. + +* Add 'charset' to Content-Type headers on various HTTP error responses to +forestall additional UTF-7-autodetect XSS issues. PHP sends only 'text/html' by +default when the script didn't specify more details, which some inconsiderate +browsers consider a license to autodetect the deadly, hard-to-escape UTF-7. +This fixes an issue with the Ajax interface error message on MSIE when +[[Manual:$wgUseAjax|$wgUseAjax]] is enabled (not default configuration); this +UTF-7 variant on a previously fixed attack vector was discovered by Moshe BA +from BugSec: http://www.bugsec.com/articles.php?Security=24 +* Trackback responses now specify XML content type + +== MediaWiki 1.7.2 == + +January 9, 2007 + +* Note about $wgUploadSizeWarning using byte +* Update to German bookstore list (de) +* (bug [[bugzilla:6680|6680]]) Added localisation for Dutch bookstore list (nl) +* (bug [[bugzilla:6708|6708]]) Minor updates to Russian translation (ru) +* (bug [[bugzilla:6730|6730]]) Clearer usage of message 'titlematch' in German +translation (de) +* Added direction mark to Special:Listredirects +* XSS fix in AJAX module + +An XSS injection vulnerability was located in the AJAX support module, +affecting MediaWiki 1.6.x and up when the optional setting +[[Manual:$wgUseAjax|$wgUseAjax]] is enabled. + +There is no danger in the default configuration, with +[[Manual:$wgUseAjax|$wgUseAjax]] off. + +If you are using an extension based on the optional AJAX module, either disable +it or upgrade to a version containing the fix: + +* 1.9: fixed in 1.9.0rc2 +* 1.8: fixed in 1.8.3 +* 1.7: fixed in 1.7.2 +* 1.6: fixed in 1.6.9 + + +== MediaWiki 1.7.1 == + +July 8, 2006 + +MediaWiki 1.7.1 is a security and bugfix maintenance release of the Summer 2006 +snapshot: + +A potential HTML/JavaScript-injection vulnerability in a debugging script has +been fixed. Only versions and configurations of PHP vulnerable to the $GLOBALS +overwrite vulnerability are affected. + +As a workaround for existing installs, profileinfo.php may simply be deleted if +it's not being used. + +* Fix for 'emailconfirmed' implicit user group +* Fix for upgrades on some versions of MySQL 4.0.x +* Fixed potential XSS in profileinfo.php +* Installer now shows clear error message about old PHP versions rather than a +confusing parse error + +== MediaWiki 1.7.0 == +July 6, 2006 + +This is the quarterly release snapshot for Summer 2006. While the code +has been running on Wikipedia for some time, installation and upgrade +bits may be less well tested. Bug fix releases may follow in the coming +days or weeks. + +MediaWiki is now using a "[[w:en:Continuous_integration|continuous +integration]]" development model with +quarterly snapshot releases. The latest development code is always kept +"ready to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature development happen +will be made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain +it from source control: [[Download from SVN]] == Changes since 1.6 == @@ -15184,6 +16115,411 @@ they will be run along with the main tests by maintenance/parserTests.php * (bug 6577) Avoid multiline parser breakage on
 with newline in attribute
 * (bug 6771) Make old revisions of MediaWiki pages available with action=raw
 
+
+== Compatibility ==
+MediaWiki 1.7 requires PHP 5 (5.1 recommended). PHP 4 is no longer supported.
+
+If you are unable to run PHP 5, you may have to stick with 1.6 for now.
+
+MySQL 3.23.x is no longer supported; some older hosts may need to upgrade.
+At this time we still recommend 4.0, but 4.1/5.0 will work fine in most cases.
+
+Experimental Oracle support has been dropped as it is unmaintained.
+
+== Upgrading ==
+Several changes to the database have been made from 1.6:
+
+* A new "langlinks" table tracks interlanguage links
+* A new "filearchive" table stores information on deleted files
+* A new "querycache_info" table stores information on query page updates
+
+To ensure that these tables are filled with data, run refreshLinks.php after
+the upgrade.
+
+If you are upgrading from MediaWiki 1.4.x or earlier, some major database
+changes are made, and there is a slightly higher chance that things could
+break. Don't forget to always back up your database before upgrading!
+
+== Configuration changes ==
+
+Some configuration options have changed:
+* $wgAllowExternalImages now defaults to off for increased security.
+* $wgLocalTZoffset was in hours, it is now using minutes.
+* Extensions may register special pages via the $wgSpecialPages array without
+forcing an early load of the SpecialPage.php class file.
+
+== Major new features ==
+
+* Deleted files can now be archived and undeleted, if you set up an appropriate
+non-web-accessible directory. Set $wgSaveDeletedFiles on and an appropriate
+directory path in $wgFileStore['deleted']['directory']
+* Experimental PostgreSQL support has been updated. It may or may not be in
+usable shape; those interested in PostgreSQL are encouraged to follow 1.8
+development.
+
+=== Caveats ===
+Some output, particularly involving user-supplied inline HTML, may not
+produce 100% valid or well-formed XHTML output. Testers are welcome to
+set $wgMimeType = "application/xhtml+xml"; to test for remaining problem
+cases, but this is not recommended on live sites. (This must be set for
+MathML to display properly in Mozilla.)
+
+= MediaWiki 1.6 =
+
+== MediaWiki 1.6.12 ==
+
+February 7, 2009
+
+This is a security update to the Spring 2006 quarterly release.
+
+A number of cross-site scripting (XSS) security vulnerabilities were discovered
+in the web-based installer (config/index.php). These vulnerabilities all
+require a live installer -- once the installer has been used to install a
+wiki,  it is deactivated.
+
+Note that cross-site scripting vulnerabilities can be used to attack any
+website in the same cookie domain. So if you have an uninstalled copy of
+MediaWiki on the same site as an active web service, MediaWiki could be used to
+attack the active service.
+
+If you are hosting an old copy of MediaWiki that you have never installed, you
+are advised to remove it from the web.
+
+== MediaWiki 1.6.11 ==
+
+December 15, 2008
+
+This is a security update to the Spring 2006 quarterly release.
+
+David Remahl of Apple's Product Security team has identified a number of
+security issues in previous releases of MediaWiki. Subsequent analysis by the
+MediaWiki development team expanded the scope of these vulnerabilities. The
+issues with a significant impact are as follows:
+
+* An XSS vulnerability affecting Internet Explorer clients for all MediaWiki
+installations with uploads enabled. [CVE-2008-5250]
+* An XSS vulnerability affecting clients with SVG scripting capability (such as
+Firefox 1.5+), for all MediaWiki installations with SVG uploads enabled.
+[CVE-2008-5250]
+* A CSRF vulnerability affecting the Special:Import feature, for all MediaWiki
+installations since the feature was introduced in 1.3.0. [CVE-2008-5252]
+
+XSS (cross-site scripting) vulnerabilities allow an attacker to steal an
+authorised user's login session, and to act as that user on the wiki. The
+authorised user must visit a web page controlled by the attacker in order to
+activate the attack. Intranet wikis are vulnerable if the attacker can
+determine the intranet URL, even if the attacker cannot access it.
+
+CSRF vulnerabilities allow an attacker to act as an authorised user on the
+wiki, but unlike an XSS vulnerability, the attacker can only act as the user in
+a specific and restricted way. The present CSRF vulnerability allows pages to
+be edited, with forged revision histories. Like an XSS vulnerability, the
+authorised user must visit the malicious web page to activate the attack.
+
+Rather than backport our SVG validation code to this ancient branch, we have
+instead disabled SVG uploads. To enable SVG uploads, please upgrade to
+MediaWiki 1.13.3 or later.
+
+The other two issues have been fixed.
+
+== MediaWiki 1.6.10 ==
+
+February 20, 2007
+
+This is a security and bug-fix update to the Spring 2006 quarterly release.
+
+An XSS injection vulnerability based on Microsoft Internet Explorer's UTF-7
+charset autodetection was located in the AJAX support module, affecting MSIE
+users on MediaWiki 1.6.x and up when the optional setting $wgUseAjax is enabled.
+
+If you are using an extension based on the optional Ajax module, either disable
+it or upgrade to a version containing the fix:
+
+* 1.9: fixed in 1.9.3
+* 1.8: fixed in 1.8.4
+* 1.7: fixed in 1.7.3
+* 1.6: fixed in 1.6.10
+
+There is no known danger in the default configuration, with $wgUseAjax off.
+
+* ([[mediazilla:8819|bug 8819]]) Fix full path disclosure with skins
+dependencies
+* Add 'charset' to Content-Type headers on various HTTP error responses to
+forestall additional UTF-7-autodetect XSS issues. PHP sends only 'text/html' by
+default when the script didn't specify more details, which some inconsiderate
+browsers consider a license to autodetect the deadly, hard-to-escape UTF-7.
+This fixes an issue with the Ajax interface error message on MSIE when
+$wgUseAjax is enabled (not default configuration); this UTF-7 variant on a
+previously fixed attack vector was discovered by Moshe BA from BugSec:
+http://www.bugsec.com/articles.php?Security=24
+* Trackback responses now specify XML content type
+
+== MediaWiki 1.6.9 ==
+
+January 9, 2007
+
+* ([[mediazilla:6621|bug 6621]]) Backported German translation for
+'eauthentsent'
+
+* ([[mediazilla:6680|bug 6680]]) Added localisation for Dutch bookstore list
+(nl)
+* ([[mediazilla:6730|bug 6730]]) Clearer usage of message 'titlematch' in
+German translation (de)
+* XSS fix in AJAX module
+
+An XSS injection vulnerability was located in the AJAX support module,
+affecting MediaWiki 1.6.x and up when the optional setting $wgUseAjax is
+enabled.
+
+There is no danger in the default configuration, with $wgUseAjax off.
+
+If you are using an extension based on the optional AJAX module, either disable
+it or upgrade to a version containing the fix:
+
+* 1.9: fixed in 1.9.0rc2
+* 1.8: fixed in 1.8.3
+* 1.7: fixed in 1.7.2
+* 1.6: fixed in 1.6.9
+
+== MediaWiki 1.6.8 ==
+
+July 8, 2006
+
+MediaWiki 1.6.8 is a security and bugfix maintenance release of the Spring 2006
+snapshot:
+
+A potential HTML/JavaScript-injection vulnerability in a debugging script has
+been fixed. Only versions and configurations of PHP vulnerable to the $GLOBALS
+overwrite vulnerability are affected.
+
+As a workaround for existing installs, profileinfo.php may simply be deleted if
+it's not being used.
+
+* ([[mediazilla:5957|bug 5957]]) Updates to Hebrew translation (he)
+* Respect language directionality when displaying arrow in
+Special:Brokenredirects
+* ([[mediazilla:6415|bug 6415]]) Typo in Parser.php
+* Fixed potential XSS in profileinfo.php
+
+== MediaWiki 1.6.7 ==
+
+June 6, 2006
+
+MediaWiki 1.6.7 is a security and bugfix maintenance release of the Spring 2006
+snapshot:
+
+An HTML/JavaScript-injection vulnerability in the edit form has been closed.
+This vulnerability was new in 1.6.0; MediaWiki versions 1.5.x or earlier are
+not affected.
+
+Extensions, comments, and  sections are now handled in
+a one-pass way which is more reliable and safer. Under earlier versions of
+MediaWiki, certain extensions could be abused to inject HTML/JavaScript into
+the page.
+
+Additional precautions are made against offsite form submissions when the
+restricted raw HTML mode is enabled.
+
+Some small localization and user interface updates are also included.
+
+*([[MediaZilla:6051|bug 6051]]) Improvement to German localisation (de)
+*([[MediaZilla:6017|bug 6017]]) Update bookstore list for German language (de)
+*([[MediaZilla:6138|bug 6138]]) Minor grammar tweak in "loginreqlink"
+*([[MediaZilla:5957|bug 5957]]) Update for Hebrew language (he)
+*Increase robustness of parser placeholders; fixes some glitches when adjacent
+to identifier-ish constructs such as URLs.
+*([[MediaZilla:5384|bug 5384]]) Fix  in 
+extension
+*Nesting of different tag extensions and comments should now work more
+consistently and more safely. A cleaner, one-pass tag strip lets the 'outer'
+tag either take source (-style) or pass it down to
+further parsing (-style). There should no longer be
+surprise expansion of foreign extensions inside HTML output, or differences in
+behavior based on the order tags are loaded.
+*([[MediaZilla:885|bug 885]]) Pre-save transform no longer silently appends
+close tags
+*Pre-save transform no longer changes the case of close tags
+*Edit security precautions in raw HTML mode, etc
+
+== MediaWiki 1.6.6 ==
+
+May 23, 2006
+
+MediaWiki 1.6.6 is a security and bugfix maintenance release.
+
+An XSS injection vector in brace replacement has been fixed, as have some
+potential problems with table parsing. Upgrading is strongly recommended for
+all users of 1.6. MediaWiki versions 1.5 and earlier are not affected.
+
+Additionally some localization and user interface updates are included.
+
+* Correct "revertpage" message in English
+* ([[MediaZilla:5507|bug 5507]]) Logouttext now uses wiki markup
+* (bugs [[MediaZilla:5857|5857]], [[MediaZilla:5957|5957]]) Update for German
+localisation (de)
+* ([[MediaZilla:5586|bug 5586]])  treated text as
+links
+* ([[MediaZilla:5957|bug 5957]]) Update for Hebrew language (he)
+* ([[MediaZilla:6025|bug 6025]]) SpecialImport: wrong message when no file
+selected
+* ([[MediaZilla:6015|bug 6015]]) EditPage: add spacing in the boxes "edit is
+minor" and "watch this"
+* ([[MediaZilla:6018|bug 6018]]) Userrights: new message when no user specified
+('nouserspecified')
+* ([[MediaZilla:6055|bug 6055]]) Fix for HTML/JS injection bug in variable
+handler (found by Nick Jenkins)
+* Reordered wiki table handling and __TOC__ extraction in the
+parser to better handle some overlapping tag cases.
+* Only the first __TOC__ is now turned into a TOC.
+* ([[MediaZilla:361|bug 361]]) URL in URL, they were almost fixed. Now they are.
+
+== MediaWiki 1.6.5 ==
+
+May 2, 2006
+
+* Rolled back the buggy patch for [[MediaZilla:5497|bug 5497]].
+
+== MediaWiki 1.6.4 ==
+
+May 2, 2006
+
+* Further improvements to Hebrew localisation
+* ([[MediaZilla:5544|bug 5544]]) Fix redirect arrow in Special:Listredirects
+for right-to-left languages
+* Replace "doubleredirectsarrow" with a content language check that picks the
+appropriate arrow
+* Remove live debugging hack which caused errors with certain database names
+* ([[MediaZilla:5510|bug 5510]]) Warning produced when using
+{{SUBPAGENAME}} in some namespaces
+* ([[MediaZilla:5548|bug 5548]]) Improvements to Indonesian localisation
+[patch: Ivan Lanin]
+* ([[MediaZilla:5403|bug 5403]]) Fix Special:Newpages RSS/Atom feeds
+* ([[MediaZilla:3359|bug 3359]]) Add hooks on completion of file upload
+* ([[MediaZilla:5184|bug 5184]]) CSS misapplied to elements in
+Special:Allmessages due to conflicting anchor identifiers
+* ([[MediaZilla:5519|bug 5519]]) Allow sidebar cache to be disabled; disable it
+by default.
+* Add $wgReservedUsernames configuration directive to block account creation/use
+* ([[MediaZilla:5576|bug 5576]]) Remove debugging hack in session check
+* ([[MediaZilla:5181|bug 5181]]) Update "nogomatch" for Slovak
+* ([[MediaZilla:5594|bug 5594]]) Id translation up to '# Login and logout
+pages' section
+* ([[MediaZilla:5536|bug 5536]]) Use content language for editing help link
+* Minor improvements to English language files
+* Improvements to German localisation files
+* ([[MediaZilla:5628|bug 5628]]) Translations for MessagesHr.php
+* (bugs [[MediaZilla:5595|5595]], [[MediaZilla:5644|5644]]) Localisation for
+Bosnian language (bs)
+* ([[MediaZilla:5592|bug 5592]]) Actions are logged with the default language
+for the wiki, not the language of the user performing the operation.
+* ([[MediaZilla:5646|bug 5646]]) Compare for identical types in wfElement()
+* Fix for concurrency problem in job queue (image description page invalidation)
+* ([[MediaZilla:5497|bug 5497]]) regeression in HTML normalization in 1.6
+(unclosed 
  • ,
    ,
    ) +* ([[MediaZilla:5709|bug 5709]]) Allow customisation of separator for categories +* ([[MediaZilla:4834|bug 4834]]) Fix XHTML output when using $wgMaxTocLevel +* Improvements to update scripts; print out the version, check for superuser +credentials before attempting a connection, and produce a friendlier error if +the connection fails +* ([[MediaZilla:5005|bug 5005]]): Fix XHTML output. +* ([[MediaZilla:5315|bug 5315]]) "Expires: -1" HTTP header made strictly valid +(using 1970 date). +* ([[MediaZilla:4825|bug 4825]]): note in DefaultSettings.php about 'profiling' +table creation +* Remove unneeded extra whitespace at top of Special:Categories +* Rewrite reassignEdits script to be more efficient; support optional updates +to recent changes table; add reporting and silent modes +* Updated initStats maintenance script +* ([[MediaZilla:5723|bug 5723]]) Don't count pages linked to from the MediaWiki +namespace as "wanted" +* ([[MediaZilla:5789|bug 5789]]) Treat "loginreqpagetext" as wikitext +* ([[MediaZilla:5796|bug 5796]]) We require MySQL >=4.0.14 + +== MediaWiki 1.6.3 == + +April 10, 2006 + +* Fix disappearing red-linked items in the watchlist editing view +* ([[MediaZilla:5512|bug 5512]]) Spacing in "page has a history" deletion +warning +* ([[MediaZilla:5508|bug 5508]]) Switch ENGINE in table statements back to +TYPE; fixes regression where some versions of MySQL 4.0.x wouldn't work +* Added note about [[Manual:$wgUrlProtocols|$wgUrlProtocols]] format change + +== MediaWiki 1.6.2 == + +April 8, 2006 + +* Further improvements to Hebrew localisation +* Fix 'copyright' message for Romanian +* ([[MediaZilla:5476|bug 5476]]) Invalid xhtml in German localization +* ([[MediaZilla:5479|bug 5479]]) Id translation for preferences tabs caption +* ([[MediaZilla:5493|bug 5493]]) Id translation for special pages +* Additional path fixes in the updater +* ([[MediaZilla:5344|bug 5344]]) Fix regression that broke slashes in extension +tag parameters + +== MediaWiki 1.6.1 == + +April 5, 2006 + +Some minor issues in the 1.6.0 release have been corrected: +* ([[MediaZilla:5458|bug 5458]]) Fix double-URL encoding in block log link in +contribs and contribs link in block log +* ([[MediaZilla:5462|bug 5462]]) Bogus missing patch warning in updater +* ([[MediaZilla:5461|bug 5461]]) Use of deprecated "showhideminor" in +Special:Recentchangeslinked +* PHP warning when allow_call_time_pass_reference is off +* Update to Finnish localization + +== MediaWiki 1.6.0 == + +April 5, 2006 + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept "ready +to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature development will take +place on the development trunk and will appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can [[Download +from SVN|obtain it from source control]]. + +=== What's new in 1.6 === + +'''User interface:''' +* The account creation form has been separated from the user login form. +* Page protection/unprotection uses a new, expanded form + +'''Templates:''' +* Categories and "what links here" now update as expected when adding or +removing links in a template. +* Template parameters can now have default values, as {{{name|default +value}}} + +'''Uploads:''' +* Optional support for rasterizing SVG images to PNG for inline display + +'''Feeds:''' +* Feed generation upgraded to Atom 1.0 +* Diffs in RSS and Atom feeds are now colored for improved readability. + +'''Database:''' +* MySQL 3.23.x support dropped; 4.0 or later required +* Experimental support for Unicode mode of MySQL 4.1/5.0 (moderately tested) +* Experimental Oracle support (not well tested!) + +'''Anti-spam extension support:''' +* [[meta:SpamBlacklist extension|SpamBlacklist extension]] now has support for +automated cleanup. +* Support for a [[meta:ConfirmEdit extension|captcha extension]] to restrict +automated spam edits. + +Numerous bug fixes and other behind-the-scenes changes have been made; see the +file HISTORY for a complete change list. + == Changes since 1.5 == * (bug 2885) More PHP 5.1 fixes: skin, search, log, undelete @@ -15928,9 +17264,49 @@ fully support the editing toolbar, but was found to be too confusing. * (bug 2139) Show page title in subtitle when viewing "read only" page * (bug 5452) Update language name for Cree +== Compatibility == +Older PHP 4.2 and 4.1 releases are no longer supported; PHP 4 users must +upgrade to 4.3 or later. ----- +MediaWiki 1.6 is the last major version to support PHP 4; future versions will +require PHP 5. + +MySQL 3.23.x is no longer supported; some older hosts may need to upgrade. +At this time we still recommend 4.0, but 4.1/5.0 will work fine in most cases. + +== Upgrading == + +Several changes to the database have been made from 1.5; these are relatively +minor but do require that the update process be run before the new code will +work properly: + +* A new "templatelinks" table tracks template inclusions. +* A new "externallinks" table tracks URL links; this can be used by a mass +spam-cleanup tool in the SpamBlacklist extension. +* A new "jobs" table stores a queue of pages to update in the background; this +is used to update links in including pages when templates are edited. + +To ensure that these tables are filled with data, run refreshLinks.php after +the upgrade. + +If you are upgrading from MediaWiki 1.4.x or earlier, some major database +changes are made, and there is a slightly higher chance that things could +break. Don't forget to always back up your database before upgrading! + +=== Caveats === + +Some output, particularly involving user-supplied inline HTML, may not produce +100% valid or well-formed XHTML output. Testers are welcome to set $wgMimeType += "application/xhtml+xml"; to test for remaining problem cases, but this is not +recommended on live sites. (This must be set for MathML to display properly in +Mozilla.) + + += MediaWiki 1.5 = + +== MediaWiki 1.5.9 == +* (bug 3359) Add hooks on completion of file upload == MediaWiki 1.5.8 == @@ -17056,7 +18432,141 @@ set $wgMimeType = "application/xhtml+xml"; to test for remaining problem cases, but this is not recommended on live sites. (This must be set for MathML to display properly in Mozilla.) ----- += MediaWiki 1.4 = + +== MediaWiki 1.4.15 == + +(released March 26, 2006) MediaWiki 1.4.15 is a security maintenance release. A +bug in decoding of certain encoded links could allow injection of raw HTML into +page output; this could potentially lead to XSS attacks. Additionally, this +release may display more correctly in IE7 betas. + +== MediaWiki 1.4.14 == +(released January 19, 2006) MediaWiki 1.4.14 is a security and bugfix +maintenance release. A bug in edit comment formatting could send PHP into an +infinite loop if certain malformed links were included. In most installations, +this would cause the script to fail after PHP's 30-second failsafe timeout. For +several other minor fixes, see the complete changelog at the end of this file. + +== MediaWiki 1.4.13 == +(released January 5, 2006) MediaWiki 1.4.13 is a security maintenance +release.Detection for uploads of Windows Metafile (.wmf) images has been added +to help protect against a client-side vulnerability in unpatched Microsoft +Windows operating systems. Sites which have enabled uploads and added +non-standard file types (such as .ogg, .doc, or .pdf) should upgrade to this +release to ensure that malicious .wmf files can't be uploaded with a fake +extension; such files could put visitors to the site at risk. For more details +on this, see: http://en.wikipedia.org/wiki/Windows_Metafile_vulnerability + +== MediaWiki 1.4.12 == +(released 2005-11-02) MediaWiki 1.4.12 is a bugfix and security maintenance +release. A change in PHP 4.4.1 broke handling of extension and +
     sections, causing garbage data to be inserted in output
    +and saved edits. This version works around the change. This release includes
    +further corrections to the inline CSS style sanitation which works around a
    +JavaScript "feature" on Microsoft Internet Explorer. Users of Microsoft
    +Internet Explorer for Windows may be vulnerable to XSS injections on prior 1.4
    +releases; users of standards-compliant browsers are not vulnerable.
    +
    +== MediaWiki 1.4.11 ==
    +(released 2005-10-05) MediaWiki 1.4.11 is a security maintenance release.
    +Unsafe handling of CSS by Microsoft Internet Explorer could be exploited to
    +produce cross-site scripting attacks by JavaScript injection to clients running
    +that browser. This release blacklists several additional variants from use in
    +HTML inline style attributes. All publicly accessible wikis are recommended to
    +upgrade to reduce the risk to visitors using Microsoft web browsers. Note: the
    +MediaWiki 1.4.x series is not compatible with PHP 5.0.5 or higher. Upgrade to
    +the 1.5.0 release if you require this version of PHP 5.
    +
    +== MediaWiki 1.4.10 ==
    +(released 2005-09-21) MediaWiki 1.4.10 is a security maintenance release. A bug
    +in edit submission handling could cause corruption of the previous revision in
    +the database if an abnormal URL was used, such as those used by some spambots.
    +Affected releases:
    +* 1.4.x <= 1.4.9; fixed in 1.4.10
    +* 1.3.x <= 1.3.15; fixed in 1.3.16
    +1.5 release candidates are not affected by this problem. All publicly editable
    +wikis are strongly recommended to upgrade immediately.
    +1.4 releases can be manually patched by changing this bit in EditPage.php:
    +
    +
    +function importFormData( &$request ) {
    +        if( $request->wasPosted() ) {
    +
    +to:
    +
    +    function importFormData( &$request ) {
    +        if( $request->getVal( 'action' ) == 'submit' && $request->wasPosted() )
    +        {
    +
    +== MediaWiki 1.4.9 ==
    +(released 2005-08-29) MediaWiki 1.4.9 is a security maintenance release. It
    +corrects two cross-site scripting security bugs:
    +*  tags were handled incorrectly when TeX rendering
    +support is off, as in the default configuration.
    +* Extension or  sections in Wiki table syntax could
    +bypass HTML style attribute restrictions for cross-site scripting attacks
    +against Microsoft Internet Explorer Wikis where the optional math support has
    +been *enabled* are not vulnerable to the first, but are vulnerable to the
    +second.
    +
    +== MediaWiki 1.4.8 ==
    +(released 2005-08-23) MediaWiki 1.4.8 is a bug fix and security maintenance
    +release. A flaw in the interaction between extensions and HTML attribute
    +sanitization was discovered which could allow unauthorized use of offsite
    +resources in style sheets, and possible exploitation of a JavaScript injection
    +feature on Microsoft Internet Explorer. This version expands the returned text
    +and properly checks it before output. Additionally, an update to
    +skins/MonoBook.php ensures that sites using the default MonoBook skin will
    +display correctly in the Internet Explorer 7 beta. (1.3 and 1.5 are not
    +affected by this bug.)
    +
    +== MediaWiki 1.4.7 ==
    +(released 2005-07-16)
    +MediaWiki 1.4.7 is a bug fix release. Those affected by the following problems
    +in 1.4.6 should upgrade:
    +* Watchlist breakage on MySQL 3.23.x and with table prefix enabled
    +* Possible breakage in watchlist, some image resizing modes on PHP 4.1.2 1.4.6
    +included a fix for a cross-site scripting vulnerability, so anyone running
    +older 1.4 releases is very strongly encouraged to upgrade as well. Note to
    +upgraders: this version of MediaWiki is known to produce a large number of
    +notice-level warnings under the newly released PHP 4.4.0. These appear however
    +to be harmless; if you encounter them add this to your LocalSettings.php to
    +suppress the notices: error_reporting( E_ALL & ~E_NOTICE ); PHP 5.1.0beta3 is
    +known to be incompatible at this time.
    +
    +== MediaWiki 1.4.6 ==
    +(released 2005-07-07) MediaWiki 1.4.6 is a bug fix and security update release.
    +Incorrect escaping of a parameter in the page move template could
    +be used to inject JavaScript code by getting a victim to visit a maliciously
    +constructed URL. Users of vulnerable releases are recommended to upgrade to
    +this release. Vulnerable versions:
    +* 1.5 preview series: n <= 1.5beta2 vulnerable, fixed in 1.5beta3
    +* 1.4 stable series: 1.4beta6 <= n <= 1.4.5 vulnerable, fixed in 1.4.6
    +* 1.3 legacy series: not vulnerable This release also includes fixes for some
    +rare bug annoying HTTP errors, a PHP 4.1.2 breakage bug, and works around some
    +template limitations introduced in 1.4.5. See the changelog at the end of this
    +file for a detailed list of bugs fixed.
    +
    +== MediaWiki 1.4.5 ==
    +(released 2005-06-03) MediaWiki 1.4.5 is a security update and bugfix release.
    +Incorrect handling of page template inclusions made it possible to inject
    +JavaScript code into HTML attributes, which could lead to cross-site scripting
    +attacks on a publicly editable wiki. Vulnerable releases and fix:
    +* 1.5 prerelease: fixed in 1.5alpha2
    +* 1.4 stable series: fixed in 1.4.5
    +* 1.3 legacy series: fixed in 1.3.13
    +* 1.2 series no longer supported; upgrade to 1.4.5 strongly recommended This
    +release also includes a number of bug fixes (see changelog below) and merges
    +some large-server load balancing patches from Wikipedia. An experimental rate
    +limiter for page edits and moves can be enabled with global, per-IP,
    +per-subnet, or per-user bases. See configuration options in
    +includes/DefaultSettings.php
    +
    +== MediaWiki 1.4.4 ==
    +(released 2005-05-04) MediaWiki 1.4.4 is a bugfix release for the 1.4 stable
    +release series. Some bugs in the installer/updater and refreshLinks maintenance
    +script were introduced in the last release and have been corrected.
     
     == MediaWiki 1.4.3 ==
     
    diff --git a/RELEASE-NOTES-1.33 b/RELEASE-NOTES-1.33
    index 6a1eaa2029..18a7b448c8 100644
    --- a/RELEASE-NOTES-1.33
    +++ b/RELEASE-NOTES-1.33
    @@ -84,6 +84,8 @@ For notes on 1.32.x and older releases, see HISTORY.
       is no longer a problem, because the code now ensures the timestamp is always
       higher than the previous one. The writes are guarded with CAS logic (check
       and set), which prevents updates that would overlap.
    +* $wgDBmysql5 (T196185) - This experimental setting, deprecated in 1.31, has
    +  been removed.
     
     === New user-facing features in 1.33 ===
     * (T96041) __EXPECTUNUSEDCATEGORY__ on a category page causes the category
    @@ -115,7 +117,7 @@ For notes on 1.32.x and older releases, see HISTORY.
     * Added jakub-onderka/php-console-highlighter 0.3.2 explicitly (dev-only).
     
     ==== Changed external libraries ====
    -* Updated OOUI from v0.29.2 to v0.31.0.
    +* Updated OOUI from v0.29.2 to v0.31.2.
     * Updated OOjs Router from pre-release to v0.2.0.
     * Updated moment from v2.19.3 to v2.24.0.
     * Updated wikimedia/xmp-reader from 0.6.0 to 0.6.2.
    @@ -413,6 +415,11 @@ because of Phabricator reports.
       changed to explicitly cast. Subclasses relying on the base-class
       implementation should check whether they need to override it now.
     * BagOStuff::add is now abstract and must explicitly be defined in subclasses.
    +* LinksDeletionUpdate is now a subclass of LinksUpdate. As a consequence,
    +  the following hooks will now be triggered upon page deletion in addition
    +  to page updates: LinksUpdateConstructed, LinksUpdate, LinksUpdateComplete.
    +  LinksUpdateAfterInsert is not triggered since deletions do not cause
    +  insertions into links tables.
     
     == Compatibility ==
     MediaWiki 1.33 requires PHP 7.0.13 or later. Although HHVM 3.18.5 or later is
    diff --git a/autoload.php b/autoload.php
    index 4172ed3d5a..528b7fe372 100644
    --- a/autoload.php
    +++ b/autoload.php
    @@ -426,6 +426,7 @@ $wgAutoloadLocalClasses = [
     	'DumpFilter' => __DIR__ . '/includes/export/DumpFilter.php',
     	'DumpGZipOutput' => __DIR__ . '/includes/export/DumpGZipOutput.php',
     	'DumpIterator' => __DIR__ . '/maintenance/dumpIterator.php',
    +	'DumpLBZip2Output' => __DIR__ . '/includes/export/DumpLBZip2Output.php',
     	'DumpLatestFilter' => __DIR__ . '/includes/export/DumpLatestFilter.php',
     	'DumpLinks' => __DIR__ . '/maintenance/dumpLinks.php',
     	'DumpMessages' => __DIR__ . '/maintenance/language/dumpMessages.php',
    @@ -850,7 +851,7 @@ $wgAutoloadLocalClasses = [
     	'Maintenance' => __DIR__ . '/maintenance/Maintenance.php',
     	'MakeTestEdits' => __DIR__ . '/maintenance/makeTestEdits.php',
     	'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
    -	'ManageForeignResources' => __DIR__ . '/maintenance/resources/manageForeignResources.php',
    +	'ManageForeignResources' => __DIR__ . '/maintenance/manageForeignResources.php',
     	'ManageJobs' => __DIR__ . '/maintenance/manageJobs.php',
     	'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
     	'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
    @@ -1107,10 +1108,10 @@ $wgAutoloadLocalClasses = [
     	'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
     	'Pingback' => __DIR__ . '/includes/Pingback.php',
     	'PoolCounter' => __DIR__ . '/includes/poolcounter/PoolCounter.php',
    +	'PoolCounterNull' => __DIR__ . '/includes/poolcounter/PoolCounterNull.php',
     	'PoolCounterRedis' => __DIR__ . '/includes/poolcounter/PoolCounterRedis.php',
     	'PoolCounterWork' => __DIR__ . '/includes/poolcounter/PoolCounterWork.php',
     	'PoolCounterWorkViaCallback' => __DIR__ . '/includes/poolcounter/PoolCounterWorkViaCallback.php',
    -	'PoolCounter_Stub' => __DIR__ . '/includes/poolcounter/PoolCounter.php',
     	'PoolWorkArticleView' => __DIR__ . '/includes/poolcounter/PoolWorkArticleView.php',
     	'PopulateArchiveRevId' => __DIR__ . '/maintenance/populateArchiveRevId.php',
     	'PopulateBacklinkNamespace' => __DIR__ . '/maintenance/populateBacklinkNamespace.php',
    @@ -1278,10 +1279,10 @@ $wgAutoloadLocalClasses = [
     	'Revision' => __DIR__ . '/includes/Revision.php',
     	'RevisionDeleteUser' => __DIR__ . '/includes/revisiondelete/RevisionDeleteUser.php',
     	'RevisionDeleter' => __DIR__ . '/includes/revisiondelete/RevisionDeleter.php',
    -	'RevisionItem' => __DIR__ . '/includes/RevisionList.php',
    -	'RevisionItemBase' => __DIR__ . '/includes/RevisionList.php',
    -	'RevisionList' => __DIR__ . '/includes/RevisionList.php',
    -	'RevisionListBase' => __DIR__ . '/includes/RevisionList.php',
    +	'RevisionItem' => __DIR__ . '/includes/revisionlist/RevisionItem.php',
    +	'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php',
    +	'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php',
    +	'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php',
     	'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
     	'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
     	'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
    diff --git a/composer.json b/composer.json
    index e0a4a74502..020f6bd880 100644
    --- a/composer.json
    +++ b/composer.json
    @@ -27,7 +27,7 @@
     		"ext-xml": "*",
     		"guzzlehttp/guzzle": "6.3.3",
     		"liuggio/statsd-php-client": "1.0.18",
    -		"oojs/oojs-ui": "0.31.0",
    +		"oojs/oojs-ui": "0.31.2",
     		"pear/mail": "1.4.1",
     		"pear/mail_mime": "1.10.2",
     		"pear/net_smtp": "1.8.1",
    @@ -64,7 +64,6 @@
     		"hamcrest/hamcrest-php": "^2.0",
     		"jakub-onderka/php-console-highlighter": "0.3.2",
     		"jakub-onderka/php-parallel-lint": "0.9.2",
    -		"jetbrains/phpstorm-stubs": "dev-master#38ff1a581b297f7901e961b8c923862ea80c3b96",
     		"justinrainbow/json-schema": "~5.2",
     		"mediawiki/mediawiki-codesniffer": "24.0.0",
     		"monolog/monolog": "~1.22.1",
    @@ -76,7 +75,7 @@
     		"wikimedia/avro": "1.8.0",
     		"wikimedia/testing-access-wrapper": "~1.0",
     		"wmde/hamcrest-html-matchers": "^0.1.0",
    -		"mediawiki/mediawiki-phan-config": "0.3.0"
    +		"mediawiki/mediawiki-phan-config": "0.5.0"
     	},
     	"replace": {
     		"symfony/polyfill-ctype": "1.99",
    diff --git a/docs/hooks.txt b/docs/hooks.txt
    index 4ef680ab0c..139123d5cf 100644
    --- a/docs/hooks.txt
    +++ b/docs/hooks.txt
    @@ -2446,10 +2446,14 @@ $userLang: the user language (Language or StubUserLang object)
     $wikiPage: the WikiPage (object) being saved
     $user: the user (object) saving the article
     $content: the new article content, as a Content object
    -$summary: the article summary (comment)
    -$isminor: minor flag
    -$iswatch: watch flag
    -$section: section #
    +&$summary: CommentStoreComment object containing the edit comment. Can be replaced with a new one.
    +$isminor: Boolean flag specifying if the edit was marked as minor.
    +$iswatch: Previously a watch flag. Currently unused, always null.
    +$section: Previously the section number being edited. Currently unused, always null.
    +$flags: All EDIT_… flags (including EDIT_MINOR) as an integer number. See WikiPage::doEditContent
    +  documentation for flags' definition.
    +$status: StatusValue object for the hook handlers resulting status. Either set $status->fatal() or
    +  return false to abort the save action.
     
     'PageContentSaveComplete': After an article has been updated.
     $wikiPage: WikiPage modified
    diff --git a/includes/ActorMigration.php b/includes/ActorMigration.php
    index 0c33eb9bfc..597b8e76e7 100644
    --- a/includes/ActorMigration.php
    +++ b/includes/ActorMigration.php
    @@ -136,11 +136,7 @@ class ActorMigration {
     	 * @return string[] [ $text, $actor ]
     	 */
     	private static function getFieldNames( $key ) {
    -		if ( isset( self::$specialFields[$key] ) ) {
    -			return self::$specialFields[$key];
    -		}
    -
    -		return [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
    +		return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
     	}
     
     	/**
    diff --git a/includes/Block.php b/includes/Block.php
    index 0693650238..700e551eb9 100644
    --- a/includes/Block.php
    +++ b/includes/Block.php
    @@ -165,13 +165,13 @@ class Block {
     			$this->setBlocker( $options['byText'] );
     		}
     
    -		$this->mReason = $options['reason'];
    -		$this->mTimestamp = wfTimestamp( TS_MW, $options['timestamp'] );
    -		$this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] );
    +		$this->setReason( $options['reason'] );
    +		$this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
    +		$this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
     
     		# Boolean settings
     		$this->mAuto = (bool)$options['auto'];
    -		$this->mHideName = (bool)$options['hideName'];
    +		$this->setHideName( (bool)$options['hideName'] );
     		$this->isHardblock( !$options['anonOnly'] );
     		$this->isAutoblocking( (bool)$options['enableAutoblock'] );
     		$this->isSitewide( (bool)$options['sitewide'] );
    @@ -296,12 +296,12 @@ class Block {
     			&& $this->mAuto == $block->mAuto
     			&& $this->isHardblock() == $block->isHardblock()
     			&& $this->isCreateAccountBlocked() == $block->isCreateAccountBlocked()
    -			&& $this->mExpiry == $block->mExpiry
    +			&& $this->getExpiry() == $block->getExpiry()
     			&& $this->isAutoblocking() == $block->isAutoblocking()
    -			&& $this->mHideName == $block->mHideName
    +			&& $this->getHideName() == $block->getHideName()
     			&& $this->isEmailBlocked() == $block->isEmailBlocked()
     			&& $this->isUsertalkEditAllowed() == $block->isUsertalkEditAllowed()
    -			&& $this->mReason == $block->mReason
    +			&& $this->getReason() == $block->getReason()
     			&& $this->isSitewide() == $block->isSitewide()
     			// Block::getRestrictions() may perform a database query, so keep it at
     			// the end.
    @@ -471,18 +471,20 @@ class Block {
     			$row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ?? null
     		) );
     
    -		$this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
    +		$this->setTimestamp( wfTimestamp( TS_MW, $row->ipb_timestamp ) );
     		$this->mAuto = $row->ipb_auto;
    -		$this->mHideName = $row->ipb_deleted;
    +		$this->setHideName( $row->ipb_deleted );
     		$this->mId = (int)$row->ipb_id;
     		$this->mParentBlockId = $row->ipb_parent_block_id;
     
     		// I wish I didn't have to do this
     		$db = wfGetDB( DB_REPLICA );
    -		$this->mExpiry = $db->decodeExpiry( $row->ipb_expiry );
    -		$this->mReason = CommentStore::getStore()
    +		$this->setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
    +		$this->setReason(
    +			CommentStore::getStore()
     			// Legacy because $row may have come from self::selectFields()
    -			->getCommentLegacy( $db, 'ipb_reason', $row )->text;
    +			->getCommentLegacy( $db, 'ipb_reason', $row )->text
    +		);
     
     		$this->isHardblock( !$row->ipb_anon_only );
     		$this->isAutoblocking( $row->ipb_enable_autoblock );
    @@ -679,7 +681,7 @@ class Block {
     	 * @return array
     	 */
     	protected function getDatabaseArray( IDatabase $dbw ) {
    -		$expiry = $dbw->encodeExpiry( $this->mExpiry );
    +		$expiry = $dbw->encodeExpiry( $this->getExpiry() );
     
     		if ( $this->forcedTargetID ) {
     			$uid = $this->forcedTargetID;
    @@ -690,7 +692,7 @@ class Block {
     		$a = [
     			'ipb_address'          => (string)$this->target,
     			'ipb_user'             => $uid,
    -			'ipb_timestamp'        => $dbw->timestamp( $this->mTimestamp ),
    +			'ipb_timestamp'        => $dbw->timestamp( $this->getTimestamp() ),
     			'ipb_auto'             => $this->mAuto,
     			'ipb_anon_only'        => !$this->isHardblock(),
     			'ipb_create_account'   => $this->isCreateAccountBlocked(),
    @@ -698,12 +700,12 @@ class Block {
     			'ipb_expiry'           => $expiry,
     			'ipb_range_start'      => $this->getRangeStart(),
     			'ipb_range_end'        => $this->getRangeEnd(),
    -			'ipb_deleted'          => intval( $this->mHideName ), // typecast required for SQLite
    +			'ipb_deleted'          => intval( $this->getHideName() ), // typecast required for SQLite
     			'ipb_block_email'      => $this->isEmailBlocked(),
     			'ipb_allow_usertalk'   => $this->isUsertalkEditAllowed(),
     			'ipb_parent_block_id'  => $this->mParentBlockId,
     			'ipb_sitewide'         => $this->isSitewide(),
    -		] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
    +		] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
     			+ ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
     
     		return $a;
    @@ -716,10 +718,10 @@ class Block {
     	protected function getAutoblockUpdateArray( IDatabase $dbw ) {
     		return [
     			'ipb_create_account'   => $this->isCreateAccountBlocked(),
    -			'ipb_deleted'          => (int)$this->mHideName, // typecast required for SQLite
    +			'ipb_deleted'          => (int)$this->getHideName(), // typecast required for SQLite
     			'ipb_allow_usertalk'   => $this->isUsertalkEditAllowed(),
     			'ipb_sitewide'         => $this->isSitewide(),
    -		] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
    +		] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
     			+ ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
     	}
     
    @@ -883,7 +885,7 @@ class Block {
     			# Check if the block is an autoblock and would exceed the user block
     			# if renewed. If so, do nothing, otherwise prolong the block time...
     			if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
    -				$this->mExpiry > self::getAutoblockExpiry( $ipblock->mTimestamp )
    +				$this->getExpiry() > self::getAutoblockExpiry( $ipblock->getTimestamp() )
     			) {
     				# Reset block timestamp to now and its expiry to
     				# $wgAutoblockExpiry in the future
    @@ -897,26 +899,28 @@ class Block {
     		wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
     		$autoblock->setTarget( $autoblockIP );
     		$autoblock->setBlocker( $this->getBlocker() );
    -		$autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )
    -			->inContentLanguage()->plain();
    +		$autoblock->setReason(
    +			wfMessage( 'autoblocker', $this->getTarget(), $this->getReason() )
    +				->inContentLanguage()->plain()
    +		);
     		$timestamp = wfTimestampNow();
    -		$autoblock->mTimestamp = $timestamp;
    +		$autoblock->setTimestamp( $timestamp );
     		$autoblock->mAuto = 1;
     		$autoblock->isCreateAccountBlocked( $this->isCreateAccountBlocked() );
     		# Continue suppressing the name if needed
    -		$autoblock->mHideName = $this->mHideName;
    +		$autoblock->setHideName( $this->getHideName() );
     		$autoblock->isUsertalkEditAllowed( $this->isUsertalkEditAllowed() );
     		$autoblock->mParentBlockId = $this->mId;
     		$autoblock->isSitewide( $this->isSitewide() );
     		$autoblock->setRestrictions( $this->getRestrictions() );
     
    -		if ( $this->mExpiry == 'infinity' ) {
    +		if ( $this->getExpiry() == 'infinity' ) {
     			# Original block was indefinite, start an autoblock now
    -			$autoblock->mExpiry = self::getAutoblockExpiry( $timestamp );
    +			$autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
     		} else {
     			# If the user is already blocked with an expiry date, we don't
     			# want to pile on top of that.
    -			$autoblock->mExpiry = min( $this->mExpiry, self::getAutoblockExpiry( $timestamp ) );
    +			$autoblock->setExpiry( min( $this->getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
     		}
     
     		# Insert the block...
    @@ -951,10 +955,10 @@ class Block {
     		$timestamp = wfTimestampNow();
     		wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" );
     
    -		if ( !$this->mExpiry ) {
    +		if ( !$this->getExpiry() ) {
     			return false;
     		} else {
    -			return $timestamp > $this->mExpiry;
    +			return $timestamp > $this->getExpiry();
     		}
     	}
     
    @@ -971,14 +975,14 @@ class Block {
     	 */
     	public function updateTimestamp() {
     		if ( $this->mAuto ) {
    -			$this->mTimestamp = wfTimestamp();
    -			$this->mExpiry = self::getAutoblockExpiry( $this->mTimestamp );
    +			$this->setTimestamp( wfTimestamp() );
    +			$this->setExpiry( self::getAutoblockExpiry( $this->getTimestamp() ) );
     
     			$dbw = wfGetDB( DB_MASTER );
     			$dbw->update( 'ipblocks',
     				[ /* SET */
    -					'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
    -					'ipb_expiry' => $dbw->timestamp( $this->mExpiry ),
    +					'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
    +					'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
     				],
     				[ /* WHERE */
     					'ipb_id' => $this->getId(),
    @@ -1032,10 +1036,7 @@ class Block {
     	 * @return int (0 for foreign users)
     	 */
     	public function getBy() {
    -		$blocker = $this->getBlocker();
    -		return ( $blocker instanceof User )
    -			? $blocker->getId()
    -			: 0;
    +		return $this->getBlocker()->getId();
     	}
     
     	/**
    @@ -1044,10 +1045,7 @@ class Block {
     	 * @return string
     	 */
     	public function getByName() {
    -		$blocker = $this->getBlocker();
    -		return ( $blocker instanceof User )
    -			? $blocker->getName()
    -			: (string)$blocker; // username
    +		return $this->getBlocker()->getName();
     	}
     
     	/**
    @@ -1074,6 +1072,46 @@ class Block {
     		return $this;
     	}
     
    +	/**
    +	 * Get the reason given for creating the block
    +	 *
    +	 * @since 1.33
    +	 * @return string
    +	 */
    +	public function getReason() {
    +		return $this->mReason;
    +	}
    +
    +	/**
    +	 * Set the reason for creating the block
    +	 *
    +	 * @since 1.33
    +	 * @param string $reason
    +	 */
    +	public function setReason( $reason ) {
    +		$this->mReason = $reason;
    +	}
    +
    +	/**
    +	 * Get whether the block hides the target's username
    +	 *
    +	 * @since 1.33
    +	 * @return bool The block hides the username
    +	 */
    +	public function getHideName() {
    +		return $this->mHideName;
    +	}
    +
    +	/**
    +	 * Set whether ths block hides the target's username
    +	 *
    +	 * @since 1.33
    +	 * @param bool $hideName The block hides the username
    +	 */
    +	public function setHideName( $hideName ) {
    +		$this->mHideName = $hideName;
    +	}
    +
     	/**
     	 * Get the system block type, if any
     	 * @since 1.29
    @@ -1660,14 +1698,45 @@ class Block {
     	}
     
     	/**
    -	 * @since 1.19
    +	 * Get the block expiry time
     	 *
    -	 * @return mixed|string
    +	 * @since 1.19
    +	 * @return string
     	 */
     	public function getExpiry() {
     		return $this->mExpiry;
     	}
     
    +	/**
    +	 * Set the block expiry time
    +	 *
    +	 * @since 1.33
    +	 * @param string $expiry
    +	 */
    +	public function setExpiry( $expiry ) {
    +		$this->mExpiry = $expiry;
    +	}
    +
    +	/**
    +	 * Get the timestamp indicating when the block was created
    +	 *
    +	 * @since 1.33
    +	 * @return string
    +	 */
    +	public function getTimestamp() {
    +		return $this->mTimestamp;
    +	}
    +
    +	/**
    +	 * Set the timestamp indicating when the block was created
    +	 *
    +	 * @since 1.33
    +	 * @param string $timestamp
    +	 */
    +	public function setTimestamp( $timestamp ) {
    +		$this->mTimestamp = $timestamp;
    +	}
    +
     	/**
     	 * Set the target for this block, and update $this->type accordingly
     	 * @param mixed $target
    @@ -1830,7 +1899,7 @@ class Block {
     			$link = $blocker;
     		}
     
    -		$reason = $this->mReason;
    +		$reason = $this->getReason();
     		if ( $reason == '' ) {
     			$reason = $context->msg( 'blockednoreason' )->text();
     		}
    @@ -1847,9 +1916,9 @@ class Block {
     			$context->getRequest()->getIP(),
     			$this->getByName(),
     			$systemBlockType ?? $this->getId(),
    -			$lang->formatExpiry( $this->mExpiry ),
    +			$lang->formatExpiry( $this->getExpiry() ),
     			(string)$intended,
    -			$lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),
    +			$lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
     		];
     	}
     
    @@ -2049,4 +2118,46 @@ class Block {
     
     		return null;
     	}
    +
    +	/**
    +	 * Check if the block should be tracked with a cookie.
    +	 *
    +	 * @since 1.33
    +	 * @param bool $isIpUser The user is logged out
    +	 * @return bool The block should be tracked with a cookie
    +	 */
    +	public function shouldTrackWithCookie( $isIpUser ) {
    +		$config = RequestContext::getMain()->getConfig();
    +		switch ( $this->getType() ) {
    +			case self::TYPE_IP:
    +			case self::TYPE_RANGE:
    +				return $isIpUser && $config->get( 'CookieSetOnIpBlock' );
    +			case self::TYPE_USER:
    +				return !$isIpUser && $config->get( 'CookieSetOnAutoblock' ) && $this->isAutoblocking();
    +			default:
    +				return false;
    +		}
    +	}
    +
    +	/**
    +	 * Check if the block prevents a user from resetting their password
    +	 *
    +	 * @since 1.33
    +	 * @return bool|null The block blocks password reset
    +	 */
    +	public function appliesToPasswordReset() {
    +		switch ( $this->getSystemBlockType() ) {
    +			case null:
    +			case 'global-block':
    +				return $this->isCreateAccountBlocked();
    +			case 'proxy':
    +				return true;
    +			case 'dnsbl':
    +			case 'wgSoftBlockRanges':
    +				return false;
    +			default:
    +				return false;
    +		}
    +	}
    +
     }
    diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
    index 3afa593254..d173d355ef 100644
    --- a/includes/DefaultSettings.php
    +++ b/includes/DefaultSettings.php
    @@ -1935,7 +1935,8 @@ $wgSearchType = null;
     $wgSearchTypeAlternatives = null;
     
     /**
    - * Table name prefix; this should be alphanumeric and not contain spaces nor hyphens
    + * Table name prefix.
    + * This should be alphanumeric, contain neither spaces nor hyphens, and end in "_"
      */
     $wgDBprefix = '';
     
    @@ -2114,26 +2115,6 @@ $wgDBerrorLog = false;
      */
     $wgDBerrorLogTZ = false;
     
    -/**
    - * Set to true to engage MySQL 4.1/5.0 charset-related features;
    - * for now will just cause sending of 'SET NAMES=utf8' on connect.
    - *
    - * @warning THIS IS EXPERIMENTAL!
    - *
    - * May break if you're not using the table defs from mysql5/tables.sql.
    - * May break if you're upgrading an existing wiki if set differently.
    - * Broken symptoms likely to include incorrect behavior with page titles,
    - * usernames, comments etc containing non-ASCII characters.
    - * Might also cause failures on the object cache and other things.
    - *
    - * Even correct usage may cause failures with Unicode supplementary
    - * characters (those not in the Basic Multilingual Plane) unless MySQL
    - * has enhanced their Unicode support.
    - *
    - * @deprecated since 1.31
    - */
    -$wgDBmysql5 = false;
    -
     /**
      * Set true to enable Oracle DCRP (supported from 11gR1 onward)
      *
    diff --git a/includes/ForeignResourceManager.php b/includes/ForeignResourceManager.php
    index e0d088a916..9fd1e4fe45 100644
    --- a/includes/ForeignResourceManager.php
    +++ b/includes/ForeignResourceManager.php
    @@ -30,10 +30,12 @@ class ForeignResourceManager {
     	private $registryFile;
     	private $libDir;
     	private $tmpParentDir;
    +	private $cacheDir;
     	private $infoPrinter;
     	private $errorPrinter;
     	private $verbosePrinter;
     	private $action;
    +	private $registry;
     
     	/**
     	 * @param string $registryFile Path to YAML file
    @@ -60,8 +62,11 @@ class ForeignResourceManager {
     
     		// Use a temporary directory under the destination directory instead
     		// of wfTempDir() because PHP's rename() does not work across file
    -		// systems, as the user's /tmp and $IP may be on different filesystems.
    -		$this->tmpParentDir = "{$this->libDir}/.tmp";
    +		// systems, and the user's /tmp and $IP may be on different filesystems.
    +		$this->tmpParentDir = "{$this->libDir}/.foreign/tmp";
    +
    +		$cacheHome = getenv( 'XDG_CACHE_HOME' ) ? realpath( getenv( 'XDG_CACHE_HOME' ) ) : false;
    +		$this->cacheDir = $cacheHome ? "$cacheHome/mw-foreign" : "{$this->libDir}/.foreign/cache";
     	}
     
     	/**
    @@ -69,18 +74,24 @@ class ForeignResourceManager {
     	 * @throws Exception
     	 */
     	public function run( $action, $module ) {
    -		if ( !in_array( $action, [ 'update', 'verify', 'make-sri' ] ) ) {
    -			throw new Exception( 'Invalid action parameter.' );
    +		$actions = [ 'update', 'verify', 'make-sri' ];
    +		if ( !in_array( $action, $actions ) ) {
    +			$this->error( "Invalid action.\n\nMust be one of " . implode( ', ', $actions ) . '.' );
    +			return false;
     		}
     		$this->action = $action;
     
    -		$registry = $this->parseBasicYaml( file_get_contents( $this->registryFile ) );
    +		$this->registry = $this->parseBasicYaml( file_get_contents( $this->registryFile ) );
     		if ( $module === 'all' ) {
    -			$modules = $registry;
    -		} elseif ( isset( $registry[ $module ] ) ) {
    -			$modules = [ $module => $registry[ $module ] ];
    +			$modules = $this->registry;
    +		} elseif ( isset( $this->registry[ $module ] ) ) {
    +			$modules = [ $module => $this->registry[ $module ] ];
     		} else {
    -			throw new Exception( 'Unknown module name.' );
    +			$this->error( "Unknown module name.\n\nMust be one of:\n" .
    +				wordwrap( implode( ', ', array_keys( $this->registry ) ), 80 ) .
    +				'.'
    +			);
    +			return false;
     		}
     
     		foreach ( $modules as $moduleName => $info ) {
    @@ -121,8 +132,8 @@ class ForeignResourceManager {
     			}
     		}
     
    -		$this->cleanUp();
     		$this->output( "\nDone!\n" );
    +		$this->cleanUp();
     		if ( $this->hasErrors ) {
     			// The verify mode should check all modules/files and fail after, not during.
     			return false;
    @@ -131,7 +142,29 @@ class ForeignResourceManager {
     		return true;
     	}
     
    +	private function cacheKey( $src, $integrity ) {
    +		$key = basename( $src ) . '_' . substr( $integrity, -12 );
    +		$key = preg_replace( '/[.\/+?=_-]+/', '_', $key );
    +		return rtrim( $key, '_' );
    +	}
    +
    +	/** @return string|false */
    +	private function cacheGet( $key ) {
    +		return Wikimedia\quietCall( 'file_get_contents', "{$this->cacheDir}/$key.data" );
    +	}
    +
    +	private function cacheSet( $key, $data ) {
    +		wfMkdirParents( $this->cacheDir );
    +		file_put_contents( "{$this->cacheDir}/$key.data", $data, LOCK_EX );
    +	}
    +
     	private function fetch( $src, $integrity ) {
    +		$key = $this->cacheKey( $src, $integrity );
    +		$data = $this->cacheGet( $key );
    +		if ( $data ) {
    +			return $data;
    +		}
    +
     		$req = MWHttpRequest::factory( $src, [ 'method' => 'GET', 'followRedirects' => false ] );
     		if ( !$req->execute()->isOK() ) {
     			throw new Exception( "Failed to download resource at {$src}" );
    @@ -144,6 +177,7 @@ class ForeignResourceManager {
     		$actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
     		if ( $integrity === $actualIntegrity ) {
     			$this->verbose( "... passed integrity check for {$src}\n" );
    +			$this->cacheSet( $key, $data );
     		} else {
     			if ( $this->action === 'make-sri' ) {
     				$this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
    @@ -271,6 +305,23 @@ class ForeignResourceManager {
     
     	private function cleanUp() {
     		wfRecursiveRemoveDir( $this->tmpParentDir );
    +
    +		// Prune the cache of files we don't recognise.
    +		$knownKeys = [];
    +		foreach ( $this->registry as $info ) {
    +			if ( $info['type'] === 'file' || $info['type'] === 'tar' ) {
    +				$knownKeys[] = $this->cacheKey( $info['src'], $info['integrity'] );
    +			} elseif ( $info['type'] === 'multi-file' ) {
    +				foreach ( $info['files'] as $file ) {
    +					$knownKeys[] = $this->cacheKey( $file['src'], $file['integrity'] );
    +				}
    +			}
    +		}
    +		foreach ( glob( "{$this->cacheDir}/*" ) as $cacheFile ) {
    +			if ( !in_array( basename( $cacheFile, '.data' ), $knownKeys ) ) {
    +				unlink( $cacheFile );
    +			}
    +		}
     	}
     
     	/**
    diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
    index 319bf6397e..55b78acf53 100644
    --- a/includes/GlobalFunctions.php
    +++ b/includes/GlobalFunctions.php
    @@ -334,6 +334,7 @@ function wfUrlencode( $s ) {
     	static $needle;
     
     	if ( is_null( $s ) ) {
    +		// Reset $needle for testing.
     		$needle = null;
     		return '';
     	}
    diff --git a/includes/Linker.php b/includes/Linker.php
    index 7dc6541c5f..df9955609c 100644
    --- a/includes/Linker.php
    +++ b/includes/Linker.php
    @@ -1077,21 +1077,23 @@ class Linker {
     	 * @since 1.16.3
     	 * @param Revision $rev
     	 * @param bool $isPublic Show only if all users can see it
    +	 * @param bool $useParentheses (optional) Wrap comments in parentheses where needed
     	 * @return string HTML
     	 */
    -	public static function revUserTools( $rev, $isPublic = false ) {
    +	public static function revUserTools( $rev, $isPublic = false, $useParentheses = true ) {
     		if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
     			$link = wfMessage( 'rev-deleted-user' )->escaped();
     		} elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
     			$userId = $rev->getUser( Revision::FOR_THIS_USER );
     			$userText = $rev->getUserText( Revision::FOR_THIS_USER );
     			$link = self::userLink( $userId, $userText )
    -				. self::userToolLinks( $userId, $userText );
    +				. self::userToolLinks( $userId, $userText, false, 0, null,
    +					$useParentheses );
     		} else {
     			$link = wfMessage( 'rev-deleted-user' )->escaped();
     		}
     		if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
    -			return ' ' . $link . '';
    +			return ' ' . $link . '';
     		}
     		return $link;
     	}
    @@ -1517,7 +1519,7 @@ class Linker {
     			$block = " " . wfMessage( 'rev-deleted-comment' )->escaped() . "";
     		}
     		if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
    -			return " $block";
    +			return " $block";
     		}
     		return $block;
     	}
    @@ -1532,9 +1534,8 @@ class Linker {
     			$stxt = wfMessage( 'historyempty' )->escaped();
     		} else {
     			$stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
    -			$stxt = wfMessage( 'parentheses' )->rawParams( $stxt )->escaped();
     		}
    -		return "$stxt";
    +		return "$stxt";
     	}
     
     	/**
    @@ -1710,12 +1711,8 @@ class Linker {
     	static function splitTrail( $trail ) {
     		$regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
     		$inside = '';
    -		if ( $trail !== '' ) {
    -			$m = [];
    -			if ( preg_match( $regex, $trail, $m ) ) {
    -				$inside = $m[1];
    -				$trail = $m[2];
    -			}
    +		if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
    +			list( , $inside, $trail ) = $m;
     		}
     		return [ $inside, $trail ];
     	}
    @@ -1884,8 +1881,7 @@ class Linker {
     
     		$attrs = [
     			'data-mw' => 'interface',
    -			'title' => $context->msg( 'tooltip-rollback' )->text(),
    -			'data-rollback-count' => (int)$editCount
    +			'title' => $context->msg( 'tooltip-rollback' )->text()
     		];
     
     		$options = [ 'known', 'noclasses' ];
    diff --git a/includes/MagicWordArray.php b/includes/MagicWordArray.php
    index fde32ce4fd..707c644a8d 100644
    --- a/includes/MagicWordArray.php
    +++ b/includes/MagicWordArray.php
    @@ -268,10 +268,7 @@ class MagicWordArray {
     			return $hash[1][$text];
     		}
     		$lc = $this->factory->getContentLanguage()->lc( $text );
    -		if ( isset( $hash[0][$lc] ) ) {
    -			return $hash[0][$lc];
    -		}
    -		return false;
    +		return $hash[0][$lc] ?? false;
     	}
     
     	/**
    diff --git a/includes/OutputPage.php b/includes/OutputPage.php
    index cb90ccf7b7..8a19c51f7a 100644
    --- a/includes/OutputPage.php
    +++ b/includes/OutputPage.php
    @@ -2984,7 +2984,7 @@ class OutputPage extends ContextSource {
     	 */
     	public function showFileRenameError( $old, $new ) {
     		wfDeprecated( __METHOD__, '1.32' );
    -		$this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->escpaed() );
    +		$this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->escaped() );
     	}
     
     	/**
    diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php
    index cbe63a33d4..01d5f9dac3 100644
    --- a/includes/PHPVersionCheck.php
    +++ b/includes/PHPVersionCheck.php
    @@ -20,6 +20,7 @@
     
     // phpcs:disable Generic.Arrays.DisallowLongArraySyntax,PSR2.Classes.PropertyDeclaration,MediaWiki.Usage.DirUsage
     // phpcs:disable Squiz.Scope.MemberVarScope.Missing,Squiz.Scope.MethodScope.Missing
    +// @phan-file-suppress PhanPluginDuplicateConditionalNullCoalescing
     /**
      * Check PHP Version, as well as for composer dependencies in entry points,
      * and display something vaguely comprehensible in the event of a totally
    diff --git a/includes/RevisionList.php b/includes/RevisionList.php
    deleted file mode 100644
    index 5243cc65dd..0000000000
    --- a/includes/RevisionList.php
    +++ /dev/null
    @@ -1,447 +0,0 @@
    -setContext( $context );
    -		$this->title = $title;
    -	}
    -
    -	/**
    -	 * Select items only where the ID is any of the specified values
    -	 * @param array $ids
    -	 */
    -	function filterByIds( array $ids ) {
    -		$this->ids = $ids;
    -	}
    -
    -	/**
    -	 * Get the internal type name of this list. Equal to the table name.
    -	 * Override this function.
    -	 * @return null
    -	 */
    -	public function getType() {
    -		return null;
    -	}
    -
    -	/**
    -	 * Initialise the current iteration pointer
    -	 */
    -	protected function initCurrent() {
    -		$row = $this->res->current();
    -		if ( $row ) {
    -			$this->current = $this->newItem( $row );
    -		} else {
    -			$this->current = false;
    -		}
    -	}
    -
    -	/**
    -	 * Start iteration. This must be called before current() or next().
    -	 * @return Revision First list item
    -	 */
    -	public function reset() {
    -		if ( !$this->res ) {
    -			$this->res = $this->doQuery( wfGetDB( DB_REPLICA ) );
    -		} else {
    -			$this->res->rewind();
    -		}
    -		$this->initCurrent();
    -		return $this->current;
    -	}
    -
    -	public function rewind() {
    -		$this->reset();
    -	}
    -
    -	/**
    -	 * Get the current list item, or false if we are at the end
    -	 * @return Revision
    -	 */
    -	public function current() {
    -		return $this->current;
    -	}
    -
    -	/**
    -	 * Move the iteration pointer to the next list item, and return it.
    -	 * @return Revision
    -	 */
    -	public function next() {
    -		$this->res->next();
    -		$this->initCurrent();
    -		return $this->current;
    -	}
    -
    -	public function key() {
    -		return $this->res ? $this->res->key() : 0;
    -	}
    -
    -	public function valid() {
    -		return $this->res ? $this->res->valid() : false;
    -	}
    -
    -	/**
    -	 * Get the number of items in the list.
    -	 * @return int
    -	 */
    -	public function length() {
    -		if ( !$this->res ) {
    -			return 0;
    -		} else {
    -			return $this->res->numRows();
    -		}
    -	}
    -
    -	/**
    -	 * Do the DB query to iterate through the objects.
    -	 * @param IDatabase $db DB object to use for the query
    -	 */
    -	abstract public function doQuery( $db );
    -
    -	/**
    -	 * Create an item object from a DB result row
    -	 * @param object $row
    -	 */
    -	abstract public function newItem( $row );
    -}
    -
    -/**
    - * Abstract base class for revision items
    - */
    -abstract class RevisionItemBase {
    -	/** @var RevisionListBase The parent */
    -	protected $list;
    -
    -	/** The database result row */
    -	protected $row;
    -
    -	/**
    -	 * @param RevisionListBase $list
    -	 * @param object $row DB result row
    -	 */
    -	public function __construct( $list, $row ) {
    -		$this->list = $list;
    -		$this->row = $row;
    -	}
    -
    -	/**
    -	 * Get the DB field name associated with the ID list.
    -	 * Override this function.
    -	 * @return null
    -	 */
    -	public function getIdField() {
    -		return null;
    -	}
    -
    -	/**
    -	 * Get the DB field name storing timestamps.
    -	 * Override this function.
    -	 * @return bool
    -	 */
    -	public function getTimestampField() {
    -		return false;
    -	}
    -
    -	/**
    -	 * Get the DB field name storing user ids.
    -	 * Override this function.
    -	 * @return bool
    -	 */
    -	public function getAuthorIdField() {
    -		return false;
    -	}
    -
    -	/**
    -	 * Get the DB field name storing user names.
    -	 * Override this function.
    -	 * @return bool
    -	 */
    -	public function getAuthorNameField() {
    -		return false;
    -	}
    -
    -	/**
    -	 * Get the DB field name storing actor ids.
    -	 * Override this function.
    -	 * @since 1.31
    -	 * @return bool
    -	 */
    -	public function getAuthorActorField() {
    -		return false;
    -	}
    -
    -	/**
    -	 * Get the ID, as it would appear in the ids URL parameter
    -	 * @return int
    -	 */
    -	public function getId() {
    -		$field = $this->getIdField();
    -		return $this->row->$field;
    -	}
    -
    -	/**
    -	 * Get the date, formatted in user's language
    -	 * @return string
    -	 */
    -	public function formatDate() {
    -		return $this->list->getLanguage()->userDate( $this->getTimestamp(),
    -			$this->list->getUser() );
    -	}
    -
    -	/**
    -	 * Get the time, formatted in user's language
    -	 * @return string
    -	 */
    -	public function formatTime() {
    -		return $this->list->getLanguage()->userTime( $this->getTimestamp(),
    -			$this->list->getUser() );
    -	}
    -
    -	/**
    -	 * Get the timestamp in MW 14-char form
    -	 * @return mixed
    -	 */
    -	public function getTimestamp() {
    -		$field = $this->getTimestampField();
    -		return wfTimestamp( TS_MW, $this->row->$field );
    -	}
    -
    -	/**
    -	 * Get the author user ID
    -	 * @return int
    -	 */
    -	public function getAuthorId() {
    -		$field = $this->getAuthorIdField();
    -		return intval( $this->row->$field );
    -	}
    -
    -	/**
    -	 * Get the author user name
    -	 * @return string
    -	 */
    -	public function getAuthorName() {
    -		$field = $this->getAuthorNameField();
    -		return strval( $this->row->$field );
    -	}
    -
    -	/**
    -	 * Get the author actor ID
    -	 * @since 1.31
    -	 * @return string
    -	 */
    -	public function getAuthorActor() {
    -		$field = $this->getAuthorActorField();
    -		return strval( $this->row->$field );
    -	}
    -
    -	/**
    -	 * Returns true if the current user can view the item
    -	 */
    -	abstract public function canView();
    -
    -	/**
    -	 * Returns true if the current user can view the item text/file
    -	 */
    -	abstract public function canViewContent();
    -
    -	/**
    -	 * Get the HTML of the list item. Should be include "
  • " tags. - * This is used to show the list in HTML form, by the special page. - */ - abstract public function getHTML(); - - /** - * Returns an instance of LinkRenderer - * @return \MediaWiki\Linker\LinkRenderer - */ - protected function getLinkRenderer() { - return MediaWikiServices::getInstance()->getLinkRenderer(); - } -} - -class RevisionList extends RevisionListBase { - public function getType() { - return 'revision'; - } - - /** - * @param IDatabase $db - * @return mixed - */ - public function doQuery( $db ) { - $conds = [ 'rev_page' => $this->title->getArticleID() ]; - if ( $this->ids !== null ) { - $conds['rev_id'] = array_map( 'intval', $this->ids ); - } - $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] ); - return $db->select( - $revQuery['tables'], - $revQuery['fields'], - $conds, - __METHOD__, - [ 'ORDER BY' => 'rev_id DESC' ], - $revQuery['joins'] - ); - } - - public function newItem( $row ) { - return new RevisionItem( $this, $row ); - } -} - -/** - * Item class for a live revision table row - */ -class RevisionItem extends RevisionItemBase { - /** @var Revision */ - protected $revision; - - /** @var RequestContext */ - protected $context; - - public function __construct( $list, $row ) { - parent::__construct( $list, $row ); - $this->revision = new Revision( $row ); - $this->context = $list->getContext(); - } - - public function getIdField() { - return 'rev_id'; - } - - public function getTimestampField() { - return 'rev_timestamp'; - } - - public function getAuthorIdField() { - return 'rev_user'; - } - - public function getAuthorNameField() { - return 'rev_user_text'; - } - - public function canView() { - return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() ); - } - - public function canViewContent() { - return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() ); - } - - public function isDeleted() { - return $this->revision->isDeleted( Revision::DELETED_TEXT ); - } - - /** - * Get the HTML link to the revision text. - * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - protected function getRevisionLink() { - $date = $this->list->getLanguage()->userTimeAndDate( - $this->revision->getTimestamp(), $this->list->getUser() ); - - if ( $this->isDeleted() && !$this->canViewContent() ) { - return htmlspecialchars( $date ); - } - $linkRenderer = $this->getLinkRenderer(); - return $linkRenderer->makeKnownLink( - $this->list->title, - $date, - [], - [ - 'oldid' => $this->revision->getId(), - 'unhide' => 1 - ] - ); - } - - /** - * Get the HTML link to the diff. - * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - protected function getDiffLink() { - if ( $this->isDeleted() && !$this->canViewContent() ) { - return $this->context->msg( 'diff' )->escaped(); - } else { - $linkRenderer = $this->getLinkRenderer(); - return $linkRenderer->makeKnownLink( - $this->list->title, - $this->list->msg( 'diff' )->text(), - [], - [ - 'diff' => $this->revision->getId(), - 'oldid' => 'prev', - 'unhide' => 1 - ] - ); - } - } - - /** - * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - public function getHTML() { - $difflink = $this->context->msg( 'parentheses' ) - ->rawParams( $this->getDiffLink() )->escaped(); - $revlink = $this->getRevisionLink(); - $userlink = Linker::revUserLink( $this->revision ); - $comment = Linker::revComment( $this->revision ); - if ( $this->isDeleted() ) { - $revlink = "$revlink"; - } - return "
  • $difflink $revlink $userlink $comment
  • "; - } -} diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 12e782d34f..73e4543a2a 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -149,7 +149,10 @@ return [ $lbConf = MWLBFactory::applyDefaultConfig( $mainConfig->get( 'LBFactoryConf' ), $mainConfig, - $services->getConfiguredReadOnlyMode() + $services->getConfiguredReadOnlyMode(), + $services->getLocalServerObjectCache(), + $services->getMainObjectStash(), + $services->getMainWANObjectCache() ); $class = MWLBFactory::getLBFactoryClass( $lbConf ); diff --git a/includes/Title.php b/includes/Title.php index d8aeb6293c..0f45839577 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -3727,6 +3727,7 @@ class Title implements LinkTarget, IDBAccessObject { // @todo: get rid of secureAndSplit, refactor parsing code. // @note: getTitleParser() returns a TitleParser implementation which does not have a // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does + /** @var MediaWikiTitleCodec $titleCodec */ $titleCodec = MediaWikiServices::getInstance()->getTitleParser(); // MalformedTitleException can be thrown here $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace ); diff --git a/includes/actions/pagers/HistoryPager.php b/includes/actions/pagers/HistoryPager.php index d3a32d0e27..b3333726a0 100644 --- a/includes/actions/pagers/HistoryPager.php +++ b/includes/actions/pagers/HistoryPager.php @@ -310,11 +310,12 @@ class HistoryPager extends ReverseChronologicalPager { $curlink = $this->curLink( $rev, $latest ); $lastlink = $this->lastLink( $rev, $next ); - $curLastlinks = $curlink . $this->historyPage->message['pipe-separator'] . $lastlink; + $curLastlinks = Html::rawElement( 'span', [], $curlink ) . + Html::rawElement( 'span', [], $lastlink ); $histLinks = Html::rawElement( 'span', - [ 'class' => 'mw-history-histlinks' ], - $this->msg( 'parentheses' )->rawParams( $curLastlinks )->escaped() + [ 'class' => 'mw-history-histlinks mw-changeslist-links' ], + $curLastlinks ); $diffButtons = $this->diffButtons( $rev, $firstInList ); @@ -362,7 +363,7 @@ class HistoryPager extends ReverseChronologicalPager { $s .= " $link"; $s .= $dirmark; $s .= " " . - Linker::revUserTools( $rev, true ) . ""; + Linker::revUserTools( $rev, true, false ) . ""; $s .= $dirmark; if ( $rev->isMinor() ) { @@ -374,12 +375,12 @@ class HistoryPager extends ReverseChronologicalPager { # Size is always public data $prevSize = $this->parentLens[$row->rev_parent_id] ?? 0; $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() ); - $fSize = Linker::formatRevisionSize( $rev->getSize() ); - $s .= ' . . ' . "$fSize $sDiff"; + $fSize = Linker::formatRevisionSize( $rev->getSize(), false ); + $s .= ' ' . "$fSize $sDiff"; } # Text following the character difference is added just before running hooks - $s2 = Linker::revComment( $rev, false, true ); + $s2 = Linker::revComment( $rev, false, true, false ); if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) { $s2 .= ' ' . $this->msg( 'updatedmarker' )->escaped() . ''; @@ -427,7 +428,11 @@ class HistoryPager extends ReverseChronologicalPager { Hooks::run( 'HistoryRevisionTools', [ $rev, &$tools, $prevRev, $user ] ); if ( $tools ) { - $s2 .= ' ' . $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped(); + $s2 .= ' ' . Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] ); + foreach ( $tools as $tool ) { + $s2 .= Html::rawElement( 'span', [], $tool ); + } + $s2 .= Html::closeElement( 'span' ); } # Tags @@ -443,7 +448,7 @@ class HistoryPager extends ReverseChronologicalPager { # Include separator between character difference and following text if ( $s2 !== '' ) { - $s .= ' . . ' . $s2; + $s .= ' ' . $s2; } $attribs = [ 'data-mw-revid' => $rev->getId() ]; @@ -480,7 +485,7 @@ class HistoryPager extends ReverseChronologicalPager { $link = htmlspecialchars( $date ); } if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $link = "$link"; + $link = "$link"; } return $link; diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index 14177edb89..673fc6b00c 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -135,7 +135,7 @@ class ApiBlock extends ApiBase { $block = Block::newFromTarget( $target, null, true ); if ( $block instanceof Block ) { - $res['expiry'] = ApiResult::formatExpiry( $block->mExpiry, 'infinite' ); + $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' ); $res['id'] = $block->getId(); } else { # should be unreachable diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index e03352554b..bff9fd0095 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -158,11 +158,9 @@ abstract class ApiFormatBase extends ApiBase { if ( !is_array( $paramSettings ) ) { return $paramSettings; - } elseif ( isset( $paramSettings[self::PARAM_DFLT] ) ) { - return $paramSettings[self::PARAM_DFLT]; - } else { - return null; } + + return $paramSettings[self::PARAM_DFLT] ?? null; } /** diff --git a/includes/api/ApiQueryRevisionsBase.php b/includes/api/ApiQueryRevisionsBase.php index 51f4d41299..565e615edd 100644 --- a/includes/api/ApiQueryRevisionsBase.php +++ b/includes/api/ApiQueryRevisionsBase.php @@ -20,6 +20,7 @@ * @file */ +use MediaWiki\Logger\LoggerFactory; use MediaWiki\Revision\RevisionAccessException; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\SlotRecord; @@ -292,69 +293,27 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase { } } - if ( $this->fld_roles ) { - $vals['roles'] = $revision->getSlotRoles(); - } - - if ( $this->needSlots ) { - $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT ); - if ( ( $this->fld_slotsha1 || $this->fetchContent ) && ( $revDel & self::IS_DELETED ) ) { - $anyHidden = true; + try { + if ( $this->fld_roles ) { + $vals['roles'] = $revision->getSlotRoles(); } - if ( $this->slotRoles === null ) { - try { - $slot = $revision->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); - } catch ( RevisionAccessException $e ) { - // Back compat: If there's no slot, there's no content, so set 'textmissing' - // @todo: Gergő says to mention T198099 as a "todo" here. - $vals['textmissing'] = true; - $slot = null; - } - if ( $slot ) { - $content = null; - $vals += $this->extractSlotInfo( $slot, $revDel, $content ); - if ( !empty( $vals['nosuchsection'] ) ) { - $this->dieWithError( - [ - 'apierror-nosuchsection-what', - wfEscapeWikiText( $this->section ), - $this->msg( 'revid', $revision->getId() ) - ], - 'nosuchsection' - ); - } - if ( $content ) { - $vals += $this->extractDeprecatedContent( $content, $revision ); - } - } - } else { - $roles = array_intersect( $this->slotRoles, $revision->getSlotRoles() ); - $vals['slots'] = [ - ApiResult::META_KVP_MERGE => true, - ]; - foreach ( $roles as $role ) { - try { - $slot = $revision->getSlot( $role, RevisionRecord::RAW ); - } catch ( RevisionAccessException $e ) { - // Don't error out here so the client can still process other slots/revisions. - // @todo: Gergő says to mention T198099 as a "todo" here. - $vals['slots'][$role]['missing'] = true; - continue; - } - $content = null; - $vals['slots'][$role] = $this->extractSlotInfo( $slot, $revDel, $content ); - // @todo Move this into extractSlotInfo() (and remove its $content parameter) - // when extractDeprecatedContent() is no more. - if ( $content ) { - $vals['slots'][$role]['contentmodel'] = $content->getModel(); - $vals['slots'][$role]['contentformat'] = $content->getDefaultFormat(); - ApiResult::setContentValue( $vals['slots'][$role], 'content', $content->serialize() ); - } + if ( $this->needSlots ) { + $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT ); + if ( ( $this->fld_slotsha1 || $this->fetchContent ) && ( $revDel & self::IS_DELETED ) ) { + $anyHidden = true; } - ApiResult::setArrayType( $vals['slots'], 'kvp', 'role' ); - ApiResult::setIndexedTagName( $vals['slots'], 'slot' ); + $vals = array_merge( $vals, $this->extractAllSlotInfo( $revision, $revDel ) ); } + } catch ( RevisionAccessException $ex ) { + // This is here so T212428 doesn't spam the log. + // TODO: find out why T212428 happens in the first place! + $vals['slotsmissing'] = true; + + LoggerFactory::getInstance( 'api-warning' )->error( + 'Failed to access revision slots', + [ 'revision' => $revision->getId(), 'exception' => $ex, ] + ); } if ( $this->fld_comment || $this->fld_parsedcomment ) { @@ -396,6 +355,79 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase { return $vals; } + /** + * Extracts information about all relevant slots. + * + * @param RevisionRecord $revision + * @param int $revDel + * + * @return array + * @throws ApiUsageException + */ + private function extractAllSlotInfo( RevisionRecord $revision, $revDel ): array { + $vals = []; + + if ( $this->slotRoles === null ) { + try { + $slot = $revision->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); + } catch ( RevisionAccessException $e ) { + // Back compat: If there's no slot, there's no content, so set 'textmissing' + // @todo: Gergő says to mention T198099 as a "todo" here. + $vals['textmissing'] = true; + $slot = null; + } + + if ( $slot ) { + $content = null; + $vals += $this->extractSlotInfo( $slot, $revDel, $content ); + if ( !empty( $vals['nosuchsection'] ) ) { + $this->dieWithError( + [ + 'apierror-nosuchsection-what', + wfEscapeWikiText( $this->section ), + $this->msg( 'revid', $revision->getId() ) + ], + 'nosuchsection' + ); + } + if ( $content ) { + $vals += $this->extractDeprecatedContent( $content, $revision ); + } + } + } else { + $roles = array_intersect( $this->slotRoles, $revision->getSlotRoles() ); + $vals['slots'] = [ + ApiResult::META_KVP_MERGE => true, + ]; + foreach ( $roles as $role ) { + try { + $slot = $revision->getSlot( $role, RevisionRecord::RAW ); + } catch ( RevisionAccessException $e ) { + // Don't error out here so the client can still process other slots/revisions. + // @todo: Gergő says to mention T198099 as a "todo" here. + $vals['slots'][$role]['missing'] = true; + continue; + } + $content = null; + $vals['slots'][$role] = $this->extractSlotInfo( $slot, $revDel, $content ); + // @todo Move this into extractSlotInfo() (and remove its $content parameter) + // when extractDeprecatedContent() is no more. + if ( $content ) { + $vals['slots'][$role]['contentmodel'] = $content->getModel(); + $vals['slots'][$role]['contentformat'] = $content->getDefaultFormat(); + ApiResult::setContentValue( + $vals['slots'][$role], + 'content', + $content->serialize() + ); + } + } + ApiResult::setArrayType( $vals['slots'], 'kvp', 'role' ); + ApiResult::setIndexedTagName( $vals['slots'], 'slot' ); + } + return $vals; + } + /** * Extract information from the SlotRecord * diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index a38d877d99..f594347ab0 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -67,8 +67,8 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['blockid'] = $block->getId(); $vals['blockedby'] = $block->getByName(); $vals['blockedbyid'] = $block->getBy(); - $vals['blockreason'] = $block->mReason; - $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->mTimestamp ); + $vals['blockreason'] = $block->getReason(); + $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->getTimestamp() ); $vals['blockexpiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' ); $vals['blockpartial'] = !$block->isSitewide(); if ( $block->getSystemBlockType() !== null ) { diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index c4a31c7845..c27b10e344 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -498,7 +498,7 @@ class ApiResult implements ApiSerializable { throw new InvalidArgumentException( 'Content value must be named' ); } $this->addContentField( $path, $name, $flags ); - $this->addValue( $path, $name, $value, $flags ); + return $this->addValue( $path, $name, $value, $flags ); } /** diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php index 455ff458f1..fe5f6c4a44 100644 --- a/includes/api/ApiStashEdit.php +++ b/includes/api/ApiStashEdit.php @@ -46,6 +46,8 @@ class ApiStashEdit extends ApiBase { const MAX_CACHE_TTL = 300; // 5 minutes const MAX_SIGNATURE_TTL = 60; + const MAX_CACHE_RECENT = 2; + public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); @@ -461,9 +463,32 @@ class ApiStashEdit extends ApiBase { ); } + if ( $ok ) { + // These blobs can waste slots in low cardinality memcached slabs + self::pruneExcessStashedEntries( $cache, $user, $key ); + } + return $ok ? true : 'store_error'; } + /** + * @param BagOStuff $cache + * @param User $user + * @param string $newKey + */ + private static function pruneExcessStashedEntries( BagOStuff $cache, User $user, $newKey ) { + $key = $cache->makeKey( 'stash-edit-recent', $user->getId() ); + + $keyList = $cache->get( $key ) ?: []; + if ( count( $keyList ) >= self::MAX_CACHE_RECENT ) { + $oldestKey = array_shift( $keyList ); + $cache->delete( $oldestKey ); + } + + $keyList[] = $newKey; + $cache->set( $key, $keyList, 2 * self::MAX_CACHE_TTL ); + } + public function getAllowedParams() { return [ 'title' => [ diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php index 12ecd7411c..fc41e4ea6a 100644 --- a/includes/api/ApiUpload.php +++ b/includes/api/ApiUpload.php @@ -417,7 +417,7 @@ class ApiUpload extends ApiBase { if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) { $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] ); if ( !$progress ) { - $this->dieWithError( 'api-upload-missingresult', 'missingresult' ); + $this->dieWithError( 'apierror-upload-missingresult', 'missingresult' ); } elseif ( !$progress['status']->isGood() ) { $this->dieStatusWithCode( $progress['status'], 'stashfailed' ); } diff --git a/includes/api/i18n/ar.json b/includes/api/i18n/ar.json index 0912dc0e75..ea67f152e4 100644 --- a/includes/api/i18n/ar.json +++ b/includes/api/i18n/ar.json @@ -347,7 +347,7 @@ "apihelp-parse-paramvalue-prop-revid": "يضيف معرِف المراجعة للصفحة التي تم تحليلها.", "apihelp-parse-paramvalue-prop-displaytitle": "يضيف العنوان في تحليل نصوص الويكي.", "apihelp-parse-paramvalue-prop-headitems": "يعطي عناصر لوضعها في <head> الصفحة.", - "apihelp-parse-paramvalue-prop-headhtml": "يعطي تحليل <head> الصفحة.", + "apihelp-parse-paramvalue-prop-headhtml": "يمنح نوع مستند محلولا، ويفتح عنصر <html>، <head> ويفتح <body> الصفحة.", "apihelp-parse-paramvalue-prop-modules": "يعطي وحدات ResourceLoader المستخدمة في الصفحة، للتحميل; استخدم mw.loader.using()، يجب طلب jsconfigvars أو encodedjsconfigvars بشكل مشترك مع modules.", "apihelp-parse-paramvalue-prop-jsconfigvars": "يعطي متغيرات تكوين جافا سكريبت الخاصة بهذه الصفحة. للتطبيق; استخدم mw.config.set().", "apihelp-parse-paramvalue-prop-encodedjsconfigvars": "يعطي متغيرات تكوين جافا سكريبت الخاصة بهذه الصفحة كسلسلة JSON.", diff --git a/includes/api/i18n/fr.json b/includes/api/i18n/fr.json index 73eb1453ba..8fc7fe01e6 100644 --- a/includes/api/i18n/fr.json +++ b/includes/api/i18n/fr.json @@ -367,7 +367,7 @@ "apihelp-parse-paramvalue-prop-revid": "Ajoute l’ID de révision de la page analysée.", "apihelp-parse-paramvalue-prop-displaytitle": "Ajoute le titre du wikitexte analysé.", "apihelp-parse-paramvalue-prop-headitems": "Fournit les éléments à mettre dans le <head> de la page.", - "apihelp-parse-paramvalue-prop-headhtml": "Fournit le <head> analysé de la page.", + "apihelp-parse-paramvalue-prop-headhtml": "Fournit le type de document, à partir de l'analyse des éléments <html>, <head> et <body> de la page.", "apihelp-parse-paramvalue-prop-modules": "Fournit les modules ResourceLoader utilisés sur la page. Pour les charger, utiliser mw.loader.using(). Soit jsconfigvars soit encodedjsconfigvars doit être demandé avec modules.", "apihelp-parse-paramvalue-prop-jsconfigvars": "Fournit les variables de configuration JavaScript spécifiques à la page. Pour les appliquer, utiliser mw.config.set().", "apihelp-parse-paramvalue-prop-encodedjsconfigvars": "Fournit les variables de configuration JavaScript spécifiques à la page comme chaîne JSON.", diff --git a/includes/api/i18n/he.json b/includes/api/i18n/he.json index 04efe36fa8..3b13904b0a 100644 --- a/includes/api/i18n/he.json +++ b/includes/api/i18n/he.json @@ -352,7 +352,7 @@ "apihelp-parse-paramvalue-prop-revid": "הוספת מזהה הגרסה של הדף המפוענח.", "apihelp-parse-paramvalue-prop-displaytitle": "הוספת הכותרת של קוד הוויקי המפוענח.", "apihelp-parse-paramvalue-prop-headitems": "נותן פריטים לשים ב־<head> של הדף.", - "apihelp-parse-paramvalue-prop-headhtml": "נותן את ה־<head> המפוענח של הדף.", + "apihelp-parse-paramvalue-prop-headhtml": "נותן doctype מפוענח, תג <html> פותח, רכיב <head>, ותג <body> פותח של הדף.", "apihelp-parse-paramvalue-prop-modules": "מתן יחידות ResourceLoader שמשמשות בדף. כדי לטעון, יש להשתמש בmw.loader.using(). יש לבקש את jsconfigvars או את encodedjsconfigvars יחד עם modules.", "apihelp-parse-paramvalue-prop-jsconfigvars": "נותן משתני הגדרות של JavaScript שייחודיים לדף הזה. כדי להחיל, יש להשתמש בmw.config.set().", "apihelp-parse-paramvalue-prop-encodedjsconfigvars": "נותן משתני הגדרות של JavaScript שייחודיים לדף הזה בתור מחרוזת JSON.", diff --git a/includes/api/i18n/ja.json b/includes/api/i18n/ja.json index 6629309c92..9e28c3993e 100644 --- a/includes/api/i18n/ja.json +++ b/includes/api/i18n/ja.json @@ -1045,6 +1045,7 @@ "apierror-mustbeloggedin": "$1にログインしている必要があります。", "apierror-noimageredirect": "画像のリダイレクトを作成する権限がありません。", "apierror-nosuchpageid": "ID $1のページはありません。", + "apierror-pagelang-disabled": "このウィキではページの言語は変更できません。", "apierror-permissiondenied": "$1に必要な権限がありません。", "apierror-permissiondenied-generic": "アクセスが拒否されました。", "apierror-readonly": "ウィキは現在読み取り専用モードです。", diff --git a/includes/api/i18n/ru.json b/includes/api/i18n/ru.json index 1b30d0073d..892cba8413 100644 --- a/includes/api/i18n/ru.json +++ b/includes/api/i18n/ru.json @@ -35,7 +35,8 @@ "Movses", "Stjn", "Edward Chernenko", - "Vlad5250" + "Vlad5250", + "Diralik" ] }, "apihelp-main-extended-description": "
    \n* [[mw:Special:MyLanguage/API:Main_page|Документация]]\n* [[mw:Special:MyLanguage/API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n
    \nСтатус: MediaWiki API — зрелый и стабильный интерфейс, активно поддерживаемый и улучшаемый. Мы стараемся избегать ломающих изменений, однако изредка они могут быть необходимы. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\nОшибочные запросы: Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n

    Тестирование: для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].

    ", @@ -415,7 +416,7 @@ "apihelp-purge-param-forcelinkupdate": "Обновить таблицы ссылок.", "apihelp-purge-param-forcerecursivelinkupdate": "Обновить таблицу ссылок для данной страницы, а также всех страниц, использующих данную как шаблон.", "apihelp-purge-example-simple": "Очистить кэш для страниц Main Page и API.", - "apihelp-purge-example-generator": "Очистить кэш первых 10 страниц в основном пространстве имен.", + "apihelp-purge-example-generator": "Очистить кэш первых 10 страниц в основном пространстве имён.", "apihelp-query-summary": "Запросить данные с и о MediaWiki.", "apihelp-query-extended-description": "Все модификации данных сначала должны запросить соответствующий токен для предотвращения злоупотреблений с вредоносных сайтов.", "apihelp-query-param-prop": "Какие использовать свойства для запрашиваемых страниц.", @@ -981,7 +982,7 @@ "apihelp-query+protectedtitles-paramvalue-prop-parsedcomment": "Добавляет распарсенное описание защиты.", "apihelp-query+protectedtitles-paramvalue-prop-expiry": "Добавляет метку времени снятия защиты.", "apihelp-query+protectedtitles-paramvalue-prop-level": "Добавляет уровень защиты.", - "apihelp-query+protectedtitles-example-simple": "Список защищенных заголовков", + "apihelp-query+protectedtitles-example-simple": "Список защищённых заголовков", "apihelp-query+protectedtitles-example-generator": "Поиск ссылок на защищённые заголовки в основном пространстве имён.", "apihelp-query+querypage-summary": "Получение списка, предоставляемого служебной страницей, основанной на QueryPage.", "apihelp-query+querypage-param-page": "Название служебной страницы. Обратите внимание: чувствительно к регистру.", @@ -1527,7 +1528,7 @@ "api-help-permissions-granted-to": "{{PLURAL:$1|Гарантируется}}: $2", "api-help-right-apihighlimits": "Использовать высокие лимиты в запросах API (медленные запросы: $1, быстрые запросы: $2). Лимиты для медленных запросов также применимы к параметрам со множеством значений.", "api-help-open-in-apisandbox": "[открыть в песочнице]", - "api-help-authmanager-general-usage": "Стандартная процедура использования этого модуля такова:\n# Запрос полей, доступных из [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] с amirequestsfor=$4, и токена $5 из [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].\n# Предоставление полей пользователю и получение его данных.\n# Запрос к этому модулю, содержащий $1returnurl или аналогичное поле.\n# Проверка поля status ответа.\n#* Если вы получили PASS или FAIL, вы закончили. Операция либо завершилась успехом, либо нет.\n#* Если вы получили UI, предоставьте новые поля польззователю и получите новые данные. Затем совершите новый запрос с параметром $1continue и новыми полями, после чего повторите пункт 4.\n#* Если вы получили REDIRECT, отправьте пользователя на redirecttarget и подождите возвращения на $1returnurl. Затем совершите запрос к этому модулю с параметром $1continue и всеми полями, содержащимися в возвращённой ссылке, и повторите пункт 4.\n#* Если вы получили RESTART, это означает, что аутентификация работает, но мы не привязали пользовательский аккаунт. Вы можете рассматривать это как UI или FAIL.", + "api-help-authmanager-general-usage": "Стандартная процедура использования этого модуля такова:\n# Запрос полей, доступных из [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] с amirequestsfor=$4, и токена $5 из [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].\n# Предоставление полей пользователю и получение его данных.\n# Запрос к этому модулю, содержащий $1returnurl или аналогичное поле.\n# Проверка поля status ответа.\n#* Если вы получили PASS или FAIL, вы закончили. Операция либо завершилась успехом, либо нет.\n#* Если вы получили UI, предоставьте новые поля пользователю и получите новые данные. Затем совершите новый запрос с параметром $1continue и новыми полями, после чего повторите пункт 4.\n#* Если вы получили REDIRECT, отправьте пользователя на redirecttarget и подождите возвращения на $1returnurl. Затем совершите запрос к этому модулю с параметром $1continue и всеми полями, содержащимися в возвращённой ссылке, и повторите пункт 4.\n#* Если вы получили RESTART, это означает, что аутентификация работает, но мы не привязали пользовательский аккаунт. Вы можете рассматривать это как UI или FAIL.", "api-help-authmanagerhelper-requests": "Использовать только эти аутентификационные запросы, с id, возвращённом из [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] с amirequestsfor=$1, или из предыдущего ответа этого модуля.", "api-help-authmanagerhelper-request": "Использовать этот аутентификационный запрос, с id, возвращённом из [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] с amirequestsfor=$1.", "api-help-authmanagerhelper-messageformat": "Формат, используемый для возвращаемых сообщений.", @@ -1630,7 +1631,7 @@ "apierror-maxchars": "Параметр $1 не может быть длиннее $2 {{PLURAL:$2|символа|символов}}", "apierror-maxlag-generic": "Ожидание сервера базы данных: $1 {{PLURAL:$1|секунда|секунды|секунд}} задержки.", "apierror-maxlag": "Ожидание $2: $1 {{PLURAL:$1|секунда|секунды|секунд}} задержки.", - "apierror-mimesearchdisabled": "Поиск по MIME отключен в жадном режиме.", + "apierror-mimesearchdisabled": "Поиск по MIME отключён в жадном режиме.", "apierror-missingcontent-pageid": "Отсутствует содержимое страницы с идентификатором $1.", "apierror-missingcontent-revid": "Отсутствует содержимое версии с идентификатором $1.", "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|Параметр|Как минимум один из параметров}} $1 обязателен.", @@ -1683,7 +1684,7 @@ "apierror-permissiondenied-generic": "Доступ запрещён.", "apierror-permissiondenied-patrolflag": "Вам нужно право patrol или patrolmarks для запроса статуса патрулирования.", "apierror-permissiondenied-unblock": "У вас нет прав снимать блокировку с участников.", - "apierror-prefixsearchdisabled": "Поиск по префиксу отключен в жадном режиме.", + "apierror-prefixsearchdisabled": "Поиск по префиксу отключён в жадном режиме.", "apierror-promised-nonwrite-api": "Заголовок HTTP Promise-Non-Write-API-Action не может быть передан в записывающие модули API.", "apierror-protect-invalidaction": "Недопустимый тип защиты «$1».", "apierror-protect-invalidlevel": "Недопустимый уровень защиты «$1».", @@ -1744,8 +1745,8 @@ "apiwarn-compare-nocontentmodel": "Модель содержимого не может быть определена, предполагается $1.", "apiwarn-deprecation-deletedrevs": "list=deletedrevs устарел. Пожалуйста, вместо него используйте prop=deletedrevisions или list=alldeletedrevisions.", "apiwarn-deprecation-httpsexpected": "Использован HTTP, где ожидался HTTPS.", - "apiwarn-deprecation-login-botpw": "Вход в основной аккаунт через action=login устарел и может быть отключен без предупреждения. Для продолжения авторизации с action=login, см.\n[[Special:BotPasswords]]. Для безопасного продолжения использования входа в основной аккаунт, см. action=clientlogin.", - "apiwarn-deprecation-login-nobotpw": "Вход в основной аккаунт через action=login не поддерживается и может быть отключен без предупреждения. Для безопасной авторизации, см. action=clientlogin.", + "apiwarn-deprecation-login-botpw": "Вход в основной аккаунт через action=login устарел и может быть отключён без предупреждения. Для продолжения авторизации с action=login, см.\n[[Special:BotPasswords]]. Для безопасного продолжения использования входа в основной аккаунт, см. action=clientlogin.", + "apiwarn-deprecation-login-nobotpw": "Вход в основной аккаунт через action=login не поддерживается и может быть отключён без предупреждения. Для безопасной авторизации, см. action=clientlogin.", "apiwarn-deprecation-login-token": "Запрос токена через action=login устарел. Используйте action=query&meta=tokens&type=login.", "apiwarn-deprecation-parameter": "Параметр $1 не поддерживается.", "apiwarn-deprecation-parse-headitems": "prop=headitems устарело с MediaWiki 1.28. Используйте prop=headhtml при создании новых HTML документов или prop=modules|jsconfigvars при обновлении документов на стороне клиента.", diff --git a/includes/auth/AuthManager.php b/includes/auth/AuthManager.php index 946decf998..0c6218e6d4 100644 --- a/includes/auth/AuthManager.php +++ b/includes/auth/AuthManager.php @@ -1006,7 +1006,7 @@ class AuthManager implements LoggerAwareInterface { if ( $block ) { $errorParams = [ $block->getTarget(), - $block->mReason ?: wfMessage( 'blockednoreason' )->text(), + $block->getReason() ?: wfMessage( 'blockednoreason' )->text(), $block->getByName() ]; diff --git a/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php b/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php index 7488fbaaae..10925b50f7 100644 --- a/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php +++ b/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php @@ -77,8 +77,8 @@ class CheckBlocksSecondaryAuthenticationProvider extends AbstractSecondaryAuthen public function testUserForCreation( $user, $autocreate, array $options = [] ) { $block = $user->isBlockedFromCreateAccount(); if ( $block ) { - if ( $block->mReason ) { - $reason = $block->mReason; + if ( $block->getReason() ) { + $reason = $block->getReason(); } else { $msg = \Message::newFromKey( 'blockednoreason' ); if ( !\RequestContext::getMain()->getUser()->isSafeToLoad() ) { diff --git a/includes/block/BlockRestriction.php b/includes/block/BlockRestriction.php index 72f6eaa9b6..cbd30c2c6f 100644 --- a/includes/block/BlockRestriction.php +++ b/includes/block/BlockRestriction.php @@ -51,7 +51,7 @@ class BlockRestriction { return []; } - $db = $db ?: wfGetDb( DB_REPLICA ); + $db = $db ?: wfGetDB( DB_REPLICA ); $result = $db->select( [ 'ipblocks_restrictions', 'page' ], @@ -73,7 +73,7 @@ class BlockRestriction { * @return bool */ public static function insert( array $restrictions ) { - if ( empty( $restrictions ) ) { + if ( !$restrictions ) { return false; } @@ -85,18 +85,20 @@ class BlockRestriction { $rows[] = $restriction->toRow(); } - if ( empty( $rows ) ) { + if ( !$rows ) { return false; } $dbw = wfGetDB( DB_MASTER ); - return $dbw->insert( + $dbw->insert( 'ipblocks_restrictions', $rows, __METHOD__, [ 'IGNORE' ] ); + + return true; } /** @@ -180,7 +182,7 @@ class BlockRestriction { $parentBlockId = (int)$parentBlockId; - $db = wfGetDb( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); $db->startAtomic( __METHOD__ ); diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php index e78dfa16bb..157d88eb34 100644 --- a/includes/cache/MessageCache.php +++ b/includes/cache/MessageCache.php @@ -540,7 +540,10 @@ class MessageCache { $res = $dbr->select( $revQuery['tables'], $revQuery['fields'], - array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ), + array_merge( $conds, [ + 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ), + 'page_latest = rev_id' // get the latest revision only + ] ), __METHOD__ . "($code)-small", [], $revQuery['joins'] diff --git a/includes/cache/localisation/LCStoreStaticArray.php b/includes/cache/localisation/LCStoreStaticArray.php index 75c8465abf..f860146804 100644 --- a/includes/cache/localisation/LCStoreStaticArray.php +++ b/includes/cache/localisation/LCStoreStaticArray.php @@ -101,8 +101,7 @@ class LCStoreStaticArray implements LCStore { return $encoded; } - $type = $encoded[0]; - $data = $encoded[1]; + list( $type, $data ) = $encoded; switch ( $type ) { case 'a': diff --git a/includes/cache/localisation/LocalisationCache.php b/includes/cache/localisation/LocalisationCache.php index 1d00d19a51..8df80138d0 100644 --- a/includes/cache/localisation/LocalisationCache.php +++ b/includes/cache/localisation/LocalisationCache.php @@ -536,7 +536,6 @@ class LocalisationCache { } } elseif ( $_fileType == 'aliases' ) { if ( isset( $aliases ) ) { - /** @suppress PhanUndeclaredVariable */ $data['aliases'] = $aliases; } } else { diff --git a/includes/changetags/ChangeTags.php b/includes/changetags/ChangeTags.php index 00eed14919..3a93e57611 100644 --- a/includes/changetags/ChangeTags.php +++ b/includes/changetags/ChangeTags.php @@ -766,7 +766,7 @@ class ChangeTags { // Return nothing. $conds[] = '0'; break; - }; + } } if ( $filterTagIds !== [] ) { diff --git a/includes/config/ConfigRepository.php b/includes/config/ConfigRepository.php index 96dc51c118..2874c334f4 100644 --- a/includes/config/ConfigRepository.php +++ b/includes/config/ConfigRepository.php @@ -71,10 +71,8 @@ class ConfigRepository implements SalvageableService { if ( !$this->has( $name, true ) ) { throw new \ConfigException( 'The configuration option ' . $name . ' does not exist.' ); } - if ( isset( $this->configItems['public'][$name] ) ) { - return $this->configItems['public'][$name]; - } - return $this->configItems['private'][$name]; + + return $this->configItems['public'][$name] ?? $this->configItems['private'][$name]; } /** diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php index 2cbe67c525..a4225a1436 100644 --- a/includes/context/RequestContext.php +++ b/includes/context/RequestContext.php @@ -345,12 +345,8 @@ class RequestContext implements IContextSource, MutableContext { $obj = Language::factory( $code ); $this->lang = $obj; } - - unset( $this->recursion ); - } - catch ( Exception $ex ) { + } finally { unset( $this->recursion ); - throw $ex; } } diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 16bde4b959..3d80bbd011 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -53,11 +53,6 @@ class DatabaseOracle extends Database { private $mFieldInfoCache = []; function __construct( array $p ) { - global $wgDBprefix; - - if ( $p['tablePrefix'] == 'get from global' ) { - $p['tablePrefix'] = $wgDBprefix; - } $p['tablePrefix'] = strtoupper( $p['tablePrefix'] ); parent::__construct( $p ); Hooks::run( 'DatabaseOraclePostInit', [ $this ] ); @@ -700,14 +695,6 @@ class DatabaseOracle extends Database { return new Blob( $b ); } - function decodeBlob( $b ) { - if ( $b instanceof Blob ) { - $b = $b->fetch(); - } - - return $b; - } - function unionQueries( $sqls, $all ) { $glue = ' UNION ALL '; @@ -969,7 +956,7 @@ class DatabaseOracle extends Database { // Defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; while ( !feof( $fp ) ) { if ( $lineCallback ) { - call_user_func( $lineCallback ); + $lineCallback(); } $line = trim( fgets( $fp, 1024 ) ); $sl = strlen( $line ) - 1; @@ -1015,7 +1002,7 @@ class DatabaseOracle extends Database { $cmd = $this->replaceVars( $cmd ); if ( $inputCallback ) { - call_user_func( $inputCallback, $cmd ); + $inputCallback( $cmd ); } $res = $this->doQuery( $cmd ); if ( $resultCallback ) { @@ -1350,10 +1337,6 @@ class DatabaseOracle extends Database { return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')'; } - function getServer() { - return $this->server; - } - public function buildGroupConcatField( $delim, $table, $field, $conds = '', $join_conds = [] ) { diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php index cb1a69dfd4..fc5b18abca 100644 --- a/includes/db/MWLBFactory.php +++ b/includes/db/MWLBFactory.php @@ -22,7 +22,6 @@ */ use MediaWiki\Logger\LoggerFactory; -use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\LBFactory; use Wikimedia\Rdbms\DatabaseDomain; @@ -39,10 +38,18 @@ abstract class MWLBFactory { * @param array $lbConf Config for LBFactory::__construct() * @param Config $mainConfig Main config object from MediaWikiServices * @param ConfiguredReadOnlyMode $readOnlyMode + * @param BagOStuff $srvCace + * @param BagOStuff $mainStash + * @param WANObjectCache $wanCache * @return array */ - public static function applyDefaultConfig( array $lbConf, Config $mainConfig, - ConfiguredReadOnlyMode $readOnlyMode + public static function applyDefaultConfig( + array $lbConf, + Config $mainConfig, + ConfiguredReadOnlyMode $readOnlyMode, + BagOStuff $srvCace, + BagOStuff $mainStash, + WANObjectCache $wanCache ) { global $wgCommandLineMode; @@ -70,6 +77,7 @@ abstract class MWLBFactory { 'defaultGroup' => $mainConfig->get( 'DBDefaultGroup' ), ]; + $serversCheck = []; // When making changes here, remember to also specify MediaWiki-specific options // for Database classes in the relevant Installer subclass. // Such as MysqlInstaller::openConnection and PostgresInstaller::openConnectionWithParams. @@ -77,6 +85,7 @@ abstract class MWLBFactory { if ( isset( $lbConf['servers'] ) ) { // Server array is already explicitly configured; leave alone } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) { + $lbConf['servers'] = []; foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) { if ( $server['type'] === 'sqlite' ) { $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ]; @@ -101,7 +110,6 @@ abstract class MWLBFactory { 'tablePrefix' => $mainConfig->get( 'DBprefix' ), 'flags' => DBO_DEFAULT, 'sqlMode' => $mainConfig->get( 'SQLMode' ), - 'utf8Mode' => $mainConfig->get( 'DBmysql5' ) ]; $lbConf['servers'][$i] = $server; @@ -121,7 +129,6 @@ abstract class MWLBFactory { 'load' => 1, 'flags' => $flags, 'sqlMode' => $mainConfig->get( 'SQLMode' ), - 'utf8Mode' => $mainConfig->get( 'DBmysql5' ) ]; if ( in_array( $server['type'], $typesWithSchema, true ) ) { $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ]; @@ -141,28 +148,41 @@ abstract class MWLBFactory { if ( !isset( $lbConf['externalClusters'] ) ) { $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' ); } + + $serversCheck = $lbConf['servers']; } elseif ( $lbConf['class'] === Wikimedia\Rdbms\LBFactoryMulti::class ) { if ( isset( $lbConf['serverTemplate'] ) ) { if ( in_array( $lbConf['serverTemplate']['type'], $typesWithSchema, true ) ) { $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' ); } $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' ); - $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get( 'DBmysql5' ); } + $serversCheck = $lbConf['serverTemplate'] ?? []; } - $services = MediaWikiServices::getInstance(); + self::sanityCheckServerConfig( $serversCheck, $mainConfig ); + $lbConf = self::applyDefaultCaching( $lbConf, $srvCace, $mainStash, $wanCache ); + + return $lbConf; + } + /** + * @param array $lbConf + * @param BagOStuff $sCache + * @param BagOStuff $mStash + * @param WANObjectCache $wCache + * @return array + */ + private static function applyDefaultCaching( + array $lbConf, BagOStuff $sCache, BagOStuff $mStash, WANObjectCache $wCache + ) { // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804) - $sCache = $services->getLocalServerObjectCache(); if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) { $lbConf['srvCache'] = $sCache; } - $mStash = $services->getMainObjectStash(); if ( $mStash->getQoS( $mStash::ATTR_EMULATION ) > $mStash::QOS_EMULATION_SQL ) { $lbConf['memStash'] = $mStash; } - $wCache = $services->getMainWANObjectCache(); if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) { $lbConf['wanCache'] = $wCache; } @@ -170,6 +190,84 @@ abstract class MWLBFactory { return $lbConf; } + /** + * @param array $servers + * @param Config $mainConfig + */ + private static function sanityCheckServerConfig( array $servers, Config $mainConfig ) { + $ldDB = $mainConfig->get( 'DBname' ); // local domain DB + $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix + + foreach ( $servers as $server ) { + $type = $server['type'] ?? null; + $srvDB = $server['dbname'] ?? null; // server DB + $srvTP = $server['tablePrefix'] ?? ''; // server table prefix + + if ( $type === 'mysql' ) { + // A DB name is not needed to connect to mysql; 'dbname' is useless. + // This field only defines the DB to use for unspecified DB domains. + if ( $srvDB !== null && $srvDB !== $ldDB ) { + self::reportMismatchedDBs( $srvDB, $ldDB ); + } + } elseif ( $type === 'postgres' ) { + if ( $srvTP !== '' ) { + self::reportIfPrefixSet( $srvTP, $type ); + } + } + + if ( $srvTP !== '' && $srvTP !== $ldTP ) { + self::reportMismatchedPrefixes( $srvTP, $ldTP ); + } + } + } + + /** + * @param string $prefix Table prefix + * @param string $dbType Database type + */ + private static function reportIfPrefixSet( $prefix, $dbType ) { + $e = new UnexpectedValueException( + "\$wgDBprefix is set to '$prefix' but the database type is '$dbType'. " . + "MediaWiki does not support using a table prefix with this RDBMS type." + ); + MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY ); + exit; + } + + /** + * @param string $srvDB Server config database + * @param string $ldDB Local DB domain database + */ + private static function reportMismatchedDBs( $srvDB, $ldDB ) { + $e = new UnexpectedValueException( + "\$wgDBservers has dbname='$srvDB' but \$wgDBname='$ldDB'. " . + "Set \$wgDBname to the database used by this wiki project. " . + "There is rarely a need to set 'dbname' in \$wgDBservers. " . + "Functions like wfWikiId(), remote wiki database access, the use " . + "of Database::getDomainId(), and other features are not reliable when " . + "\$wgDBservers does not match the local wiki database/prefix." + ); + MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY ); + exit; + } + + /** + * @param string $srvTP Server config table prefix + * @param string $ldTP Local DB domain database + */ + private static function reportMismatchedPrefixes( $srvTP, $ldTP ) { + $e = new UnexpectedValueException( + "\$wgDBservers has tablePrefix='$srvTP' but \$wgDBprefix='$ldTP'. " . + "Set \$wgDBprefix to the table prefix used by this wiki project. " . + "There is rarely a need to set 'tablePrefix' in \$wgDBservers. " . + "Functions like wfWikiId(), remote wiki database access, the use " . + "of Database::getDomainId(), and other features are not reliable when " . + "\$wgDBservers does not match the local wiki database/prefix." + ); + MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY ); + exit; + } + /** * Returns the LBFactory class to use and the load balancer configuration. * diff --git a/includes/deferred/DeferredUpdates.php b/includes/deferred/DeferredUpdates.php index 67b5490511..3043c1052f 100644 --- a/includes/deferred/DeferredUpdates.php +++ b/includes/deferred/DeferredUpdates.php @@ -263,7 +263,8 @@ class DeferredUpdates { if ( $mode === 'enqueue' && $update instanceof EnqueueableDataUpdate ) { // Run only the job enqueue logic to complete the update later $spec = $update->getAsJobSpecification(); - JobQueueGroup::singleton( $spec['wiki'] )->push( $spec['job'] ); + $domain = $spec['domain'] ?? $spec['wiki']; + JobQueueGroup::singleton( $domain )->push( $spec['job'] ); } elseif ( $update instanceof TransactionRoundDefiningUpdate ) { $update->doUpdate(); } else { diff --git a/includes/deferred/EnqueueableDataUpdate.php b/includes/deferred/EnqueueableDataUpdate.php index ffeb740d41..3a77f54156 100644 --- a/includes/deferred/EnqueueableDataUpdate.php +++ b/includes/deferred/EnqueueableDataUpdate.php @@ -9,7 +9,7 @@ */ interface EnqueueableDataUpdate { /** - * @return array (wiki => wiki ID, job => IJobSpecification) + * @return array (domain => DB domain ID, job => IJobSpecification) */ public function getAsJobSpecification(); } diff --git a/includes/deferred/LinksDeletionUpdate.php b/includes/deferred/LinksDeletionUpdate.php index 0743dbec2f..0e24134770 100644 --- a/includes/deferred/LinksDeletionUpdate.php +++ b/includes/deferred/LinksDeletionUpdate.php @@ -21,22 +21,16 @@ */ use MediaWiki\MediaWikiServices; use Wikimedia\ScopedCallback; -use Wikimedia\Rdbms\IDatabase; /** * Update object handling the cleanup of links tables after a page was deleted. */ -class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate { +class LinksDeletionUpdate extends LinksUpdate implements EnqueueableDataUpdate { /** @var WikiPage */ protected $page; - /** @var int */ - protected $pageId; /** @var string */ protected $timestamp; - /** @var IDatabase */ - private $db; - /** * @param WikiPage $page Page we are updating * @param int|null $pageId ID of the page we are updating [optional] @@ -44,63 +38,37 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate { * @throws MWException */ function __construct( WikiPage $page, $pageId = null, $timestamp = null ) { - parent::__construct(); - $this->page = $page; if ( $pageId ) { - $this->pageId = $pageId; // page ID at time of deletion + $this->mId = $pageId; // page ID at time of deletion } elseif ( $page->exists() ) { - $this->pageId = $page->getId(); + $this->mId = $page->getId(); } else { throw new InvalidArgumentException( "Page ID not known. Page doesn't exist?" ); } $this->timestamp = $timestamp ?: wfTimestampNow(); + + $fakePO = new ParserOutput(); + $fakePO->setCacheTime( $timestamp ); + parent::__construct( $page->getTitle(), $fakePO, false ); } - public function doUpdate() { + protected function doIncrementalUpdate() { $services = MediaWikiServices::getInstance(); $config = $services->getMainConfig(); $lbFactory = $services->getDBLoadBalancerFactory(); $batchSize = $config->get( 'UpdateRowsPerQuery' ); - // Page may already be deleted, so don't just getId() - $id = $this->pageId; - - if ( $this->ticket ) { - // Make sure all links update threads see the changes of each other. - // This handles the case when updates have to batched into several COMMITs. - $scopedLock = LinksUpdate::acquirePageLock( $this->getDB(), $id ); - if ( !$scopedLock ) { - throw new RuntimeException( "Could not acquire lock for page ID '{$id}'." ); - } - } + $id = $this->mId; + $title = $this->mTitle; - $title = $this->page->getTitle(); $dbw = $this->getDB(); // convenience - // Delete restrictions for it - $dbw->delete( 'page_restrictions', [ 'pr_page' => $id ], __METHOD__ ); + parent::doIncrementalUpdate(); - // Fix category table counts - $cats = $dbw->selectFieldValues( - 'categorylinks', - 'cl_to', - [ 'cl_from' => $id ], - __METHOD__ - ); - $catBatches = array_chunk( $cats, $batchSize ); - foreach ( $catBatches as $catBatch ) { - $this->page->updateCategoryCounts( [], $catBatch, $id ); - if ( count( $catBatches ) > 1 ) { - // Only sacrifice atomicity if necessary due to size - $lbFactory->commitAndWaitForReplication( - __METHOD__, $this->ticket, [ 'domain' => $dbw->getDomainID() ] - ); - } - } - - // Refresh counts on categories that should be empty now + // Typically, a category is empty when deleted, so check that we don't leave + // spurious row in the category table. if ( $title->getNamespace() === NS_CATEGORY ) { // T166757: do the update after the main job DB commit DeferredUpdates::addCallableUpdate( function () use ( $title ) { @@ -109,52 +77,11 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate { } ); } - $this->batchDeleteByPK( - 'pagelinks', - [ 'pl_from' => $id ], - [ 'pl_from', 'pl_namespace', 'pl_title' ], - $batchSize - ); - $this->batchDeleteByPK( - 'imagelinks', - [ 'il_from' => $id ], - [ 'il_from', 'il_to' ], - $batchSize - ); - $this->batchDeleteByPK( - 'categorylinks', - [ 'cl_from' => $id ], - [ 'cl_from', 'cl_to' ], - $batchSize - ); - $this->batchDeleteByPK( - 'templatelinks', - [ 'tl_from' => $id ], - [ 'tl_from', 'tl_namespace', 'tl_title' ], - $batchSize - ); - $this->batchDeleteByPK( - 'externallinks', - [ 'el_from' => $id ], - [ 'el_id' ], - $batchSize - ); - $this->batchDeleteByPK( - 'langlinks', - [ 'll_from' => $id ], - [ 'll_from', 'll_lang' ], - $batchSize - ); - $this->batchDeleteByPK( - 'iwlinks', - [ 'iwl_from' => $id ], - [ 'iwl_from', 'iwl_prefix', 'iwl_title' ], - $batchSize - ); + // Delete restrictions for the deleted page + $dbw->delete( 'page_restrictions', [ 'pr_page' => $id ], __METHOD__ ); - // Delete any redirect entry or page props entries + // Delete any redirect entry $dbw->delete( 'redirect', [ 'rd_from' => $id ], __METHOD__ ); - $dbw->delete( 'page_props', [ 'pp_page' => $id ], __METHOD__ ); // Find recentchanges entries to clean up... $rcIdsForTitle = $dbw->selectFieldValues( @@ -191,46 +118,14 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate { ScopedCallback::consume( $scopedLock ); } - private function batchDeleteByPK( $table, array $conds, array $pk, $bSize ) { - $services = MediaWikiServices::getInstance(); - $lbFactory = $services->getDBLoadBalancerFactory(); - $dbw = $this->getDB(); // convenience - - $res = $dbw->select( $table, $pk, $conds, __METHOD__ ); - - $pkDeleteConds = []; - foreach ( $res as $row ) { - $pkDeleteConds[] = $dbw->makeList( (array)$row, LIST_AND ); - if ( count( $pkDeleteConds ) >= $bSize ) { - $dbw->delete( $table, $dbw->makeList( $pkDeleteConds, LIST_OR ), __METHOD__ ); - $lbFactory->commitAndWaitForReplication( - __METHOD__, $this->ticket, [ 'domain' => $dbw->getDomainID() ] - ); - $pkDeleteConds = []; - } - } - - if ( $pkDeleteConds ) { - $dbw->delete( $table, $dbw->makeList( $pkDeleteConds, LIST_OR ), __METHOD__ ); - } - } - - protected function getDB() { - if ( !$this->db ) { - $this->db = wfGetDB( DB_MASTER ); - } - - return $this->db; - } - public function getAsJobSpecification() { return [ - 'wiki' => WikiMap::getWikiIdFromDbDomain( $this->getDB()->getDomainID() ), - 'job' => new JobSpecification( + 'domain' => $this->getDB()->getDomainID(), + 'job' => new JobSpecification( 'deleteLinks', - [ 'pageId' => $this->pageId, 'timestamp' => $this->timestamp ], + [ 'pageId' => $this->mId, 'timestamp' => $this->timestamp ], [ 'removeDuplicates' => true ], - $this->page->getTitle() + $this->mTitle ) ]; } diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php index 7a31e26253..14f86b7c5e 100644 --- a/includes/deferred/LinksUpdate.php +++ b/includes/deferred/LinksUpdate.php @@ -122,7 +122,11 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { parent::__construct(); $this->mTitle = $title; - $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE ); + + if ( !$this->mId ) { + // NOTE: subclasses may initialize mId before calling this constructor! + $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE ); + } if ( !$this->mId ) { throw new InvalidArgumentException( @@ -1180,7 +1184,7 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { /** * @return IDatabase */ - private function getDB() { + protected function getDB() { if ( !$this->db ) { $this->db = wfGetDB( DB_MASTER ); } @@ -1205,7 +1209,7 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { } return [ - 'wiki' => WikiMap::getWikiIdFromDbDomain( $this->getDB()->getDomainID() ), + 'domain' => $this->getDB()->getDomainID(), 'job' => new JobSpecification( 'refreshLinksPrioritized', [ diff --git a/includes/diff/DiffEngine.php b/includes/diff/DiffEngine.php index edf844417e..546a12cb79 100644 --- a/includes/diff/DiffEngine.php +++ b/includes/diff/DiffEngine.php @@ -456,9 +456,7 @@ class DiffEngine { // need to store these so we don't lose them when they're // overwritten by the recursion - $len = $snake[2]; - $startx = $snake[0]; - $starty = $snake[1]; + list( $startx, $starty, $len ) = $snake; // the middle snake is part of the LCS, store it for ( $i = 0; $i < $len; ++$i ) { diff --git a/includes/export/DumpLBZip2Output.php b/includes/export/DumpLBZip2Output.php new file mode 100644 index 0000000000..b923995c8d --- /dev/null +++ b/includes/export/DumpLBZip2Output.php @@ -0,0 +1,39 @@ + + * Copyright © 2019 Wikimedia Foundation Inc. + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * @ingroup Dump + * @since 1.33 + */ +class DumpLBZip2Output extends DumpPipeOutput { + /** + * @param string $file + */ + function __construct( $file ) { + # use only one core + parent::__construct( "lbzip2 -n 1", $file ); + } +} diff --git a/includes/externalstore/ExternalStore.php b/includes/externalstore/ExternalStore.php index de7d1a4c48..9cf8e15d83 100644 --- a/includes/externalstore/ExternalStore.php +++ b/includes/externalstore/ExternalStore.php @@ -197,7 +197,7 @@ class ExternalStore { $msg = 'read only'; } else { $url = $store->store( $path, $data ); - if ( strlen( $url ) ) { + if ( $url !== false ) { return $url; // a store accepted the write; done! } $msg = 'operation failed'; diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index c49810cfeb..3a75720cdf 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -184,11 +184,7 @@ class ForeignAPIFile extends File { * null on error */ public function getExtendedMetadata() { - if ( isset( $this->mInfo['extmetadata'] ) ) { - return $this->mInfo['extmetadata']; - } - - return null; + return $this->mInfo['extmetadata'] ?? null; } /** diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php index 5ede631aa3..9de7eb8408 100644 --- a/includes/gallery/TraditionalImageGallery.php +++ b/includes/gallery/TraditionalImageGallery.php @@ -72,11 +72,9 @@ class TraditionalImageGallery extends ImageGalleryBase { $lang = $this->getRenderLang(); # Output each image... foreach ( $this->mImages as $pair ) { + // "text" means "caption" here /** @var Title $nt */ - $nt = $pair[0]; - $text = $pair[1]; # "text" means "caption" here - $alt = $pair[2]; - $link = $pair[3]; + list( $nt, $text, $alt, $link ) = $pair; $descQuery = false; if ( $nt->getNamespace() === NS_FILE ) { diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 82cbb40d0c..ee0da7b64f 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -605,7 +605,7 @@ class HTMLForm extends ContextSource { $valid = true; $hoistedErrors = Status::newGood(); if ( $this->mValidationErrorMessage ) { - foreach ( (array)$this->mValidationErrorMessage as $error ) { + foreach ( $this->mValidationErrorMessage as $error ) { $hoistedErrors->fatal( ...$error ); } } else { @@ -700,8 +700,8 @@ class HTMLForm extends ContextSource { /** * Set a message to display on a validation error. * - * @param string|array $msg String or Array of valid inputs to wfMessage() - * (so each entry can be either a String or Array) + * @param array $msg Array of valid inputs to wfMessage() + * (so each entry must itself be an array of arguments) * * @return HTMLForm $this for chaining calls (since 1.20) */ diff --git a/includes/htmlform/HTMLFormField.php b/includes/htmlform/HTMLFormField.php index 818474d47e..16dc46591b 100644 --- a/includes/htmlform/HTMLFormField.php +++ b/includes/htmlform/HTMLFormField.php @@ -586,7 +586,7 @@ abstract class HTMLFormField { // It might look weird, but it'll work OK. return $this->getFieldLayoutOOUI( new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ), - [ 'infusable' => false, 'align' => 'top' ] + [ 'align' => 'top' ] ); } diff --git a/includes/htmlform/OOUIHTMLForm.php b/includes/htmlform/OOUIHTMLForm.php index 49cbdee6e0..e7e3ff6333 100644 --- a/includes/htmlform/OOUIHTMLForm.php +++ b/includes/htmlform/OOUIHTMLForm.php @@ -151,13 +151,11 @@ class OOUIHTMLForm extends HTMLForm { 'expanded' => false, 'padded' => true, 'framed' => true, - 'infusable' => false, ] ); $layout->appendContent( new OOUI\FieldsetLayout( [ 'label' => $legend, - 'infusable' => false, 'items' => [ new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $section ) diff --git a/includes/htmlform/fields/HTMLDateTimeField.php b/includes/htmlform/fields/HTMLDateTimeField.php index 7b59a1d6df..ffdf5f83d2 100644 --- a/includes/htmlform/fields/HTMLDateTimeField.php +++ b/includes/htmlform/fields/HTMLDateTimeField.php @@ -171,11 +171,19 @@ class HTMLDateTimeField extends HTMLTextField { } } - return new MediaWiki\Widget\DateTimeInputWidget( $params ); + if ( $this->mType === 'date' ) { + return new MediaWiki\Widget\DateInputWidget( $params ); + } else { + return new MediaWiki\Widget\DateTimeInputWidget( $params ); + } } protected function getOOUIModules() { - return [ 'mediawiki.widgets.datetime' ]; + if ( $this->mType === 'date' ) { + return [ 'mediawiki.widgets.DateInputWidget' ]; + } else { + return [ 'mediawiki.widgets.datetime' ]; + } } protected function shouldInfuseOOUI() { diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php index 7a92807e0c..750f10870a 100644 --- a/includes/installer/DatabaseUpdater.php +++ b/includes/installer/DatabaseUpdater.php @@ -410,9 +410,7 @@ abstract class DatabaseUpdater { $this->updatesSkipped = []; foreach ( $updates as $funcList ) { - $func = $funcList[0]; - $args = $funcList[1]; - $origParams = $funcList[2]; + list( $func, $args, $origParams ) = $funcList; $func( ...$args ); flush(); $this->updatesSkipped[] = $origParams; diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index 0bc0a8362f..a954008516 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -1501,7 +1501,7 @@ abstract class Installer { $data = $registry->readFromQueue( $queue ); $wgAutoloadClasses += $data['autoload']; - /** @suppress PhanUndeclaredVariable $wgHooks is set by DefaultSettings */ + // @phan-suppress-next-line PhanUndeclaredVariable $wgHooks is set by DefaultSettings $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? []; if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { diff --git a/includes/installer/i18n/fr.json b/includes/installer/i18n/fr.json index 79a1ebba96..2148e94fe3 100644 --- a/includes/installer/i18n/fr.json +++ b/includes/installer/i18n/fr.json @@ -90,7 +90,7 @@ "config-apcu": "[https://secure.php.net/apcu APCu] est installé", "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] est installé", "config-no-cache-apcu": "Attention : impossible de trouver [https://secure.php.net/apcu APCu] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa mise en cache d’objets n’est pas activée.", - "config-mod-security": "Attention : votre serveur web a [https://modsecurity.org/ mod_security] activé. S’il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d’autres applications qui permettent aux utilisateurs de publier un contenu quelconque. Si possible, ceci devrait être désactivé. Sinon, reportez-vous à [https://modsecurity.org/documentation/ la documentation de mod_security] ou contactez l’assistance de votre hébergeur si vous rencontrez des erreurs aléatoires.", + "config-mod-security": "Attention : votre serveur web a activé [https://modsecurity.org/ mod_security]/mod_security2 . Dans plusieurs configurations communes cela pose des problèmes à MediaWiki ou à d’autres applications qui permettent aux utilisateurs de publier un contenu quelconque. \nSi possible, ceci devrait être désactivé. Sinon, reportez-vous à [https://modsecurity.org/documentation/ la documentation de mod_security] ou contactez l’assistance de votre hébergeur si vous rencontrez des erreurs aléatoires.", "config-diff3-bad": "L’utilitaire de comparaison de texte GNU diff3 est introuvable. Vous pouvez l’ignorer pour le moment, mais cela peut provoquer des conflits de modification plus souvent.", "config-git": "Logiciel de contrôle de version Git trouvé : $1.", "config-git-bad": "Logiciel de contrôle de version Git non trouvé. Vous pouvez l’ignorer pour le moment. Notez que Special:Version n’affichera pas les hachages de validation.", diff --git a/includes/installer/i18n/pms.json b/includes/installer/i18n/pms.json index f928e20bae..9d417680af 100644 --- a/includes/installer/i18n/pms.json +++ b/includes/installer/i18n/pms.json @@ -77,7 +77,7 @@ "config-uploads-not-safe": "'''Avis:''' Sò dossié stàndard për carié $1 a l'é vulneràbil a l'esecussion ëd qualsëssìa senari.\nBele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.", "config-no-cli-uploads-check": "'''Avis:''' Toa cartela predefinìa për j-amportassion ($1) a l'é nen controlà a propòsit ëd la vulnerabilità\nd'esecussion ëd senari arbitrari durant l'istalassion CLI.", "config-brokenlibxml": "Sò sistema a l'ha na combinassion ëd version PHP e libxml2 che a l'ha dij bigat e a peul provoché la corussion ëd dat ëstërmà an MediaWiki e d'àutre aplicassion për l'aragnà.\nCh'a agiorna a PHP 5.2.9 o pi neuv e libxml2 2.7.3 o pi neuv ([https://bugs.php.net/bug.php?id=45996 bigat archivià con PHP]).\nAnstalassion abortìa.", - "config-suhosin-max-value-length": "Suhosin a l'é instalà e a lìmita la longheur dël paràmetr GET a $1 byte. Ël component ResourceLoader ëd MediaWiki a travajerà an rispetand ës lìmit, ma sòn a degraderà le prestassion. Se possìbil, a dovrìa amposté suhosin.get.max_value_length a 1024 o pi àut an php.ini, e amposté $wgResourceLoaderMaxQueryLength al midem valor an LocalSettings.php .", + "config-suhosin-max-value-length": "Suhosin a l'é instalà e a lìmita la longheur dël paràmetr GET a $1 byte. Ël component ResourceLoader ëd MediaWiki a travajerà an rispetand ës lìmit, ma sòn a degraderà le prestassion. Se possìbil, a dovrìa amposté suhosin.get.max_value_length a 1024 o pi àut an php.ini, e amposté $wgResourceLoaderMaxQueryLength al midem valor an LocalSettings.php.", "config-db-type": "Sòrt ëd base ëd dàit:", "config-db-host": "Ospitant ëd la base ëd dàit:", "config-db-host-help": "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anserissa ambelessì ël nòm dl'ospitant o l'adrëssa IP.\n\nS'a deuvra n'ospitalità partagià, sò fornidor d'ospitalità a dovrìa deje ël nòm dl'ospitant giust ant soa documentassion.\n\nSe a anstala su un servent Windows e a deuvra MySQL, dovré «localhost» a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva «127.0.0.1» com adrëssa IP local.\n\nS'a deuvra PostgresSQL, ch'a lassa sto camp bianch për coleghesse a travers un socket UNIX.", diff --git a/includes/installer/i18n/ru.json b/includes/installer/i18n/ru.json index de6d7310d7..ccadc13803 100644 --- a/includes/installer/i18n/ru.json +++ b/includes/installer/i18n/ru.json @@ -27,7 +27,8 @@ "Facenapalm", "Movses", "Vlad5250", - "Athena Atterdag" + "Athena Atterdag", + "Diralik" ] }, "config-desc": "Инсталлятор MediaWiki", @@ -270,7 +271,7 @@ "config-cc-not-chosen": "Выберите, какую лицензию Creative Commons Вы хотите использовать, и нажмите кнопку \"proceed\".", "config-advanced-settings": "Дополнительные настройки", "config-cache-options": "Параметры кэширования объектов:", - "config-cache-help": "Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.\nДля средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.", + "config-cache-help": "Кэширование объектов используется для повышения скорости MediaWiki путём кэширования часто используемых данных.\nДля средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.", "config-cache-none": "Без кэширования (никакой функционал не теряется, но крупные вики-сайты могут работать медленнее)", "config-cache-accel": "Кэширование PHP-объектов (APC, APCu или WinCache)", "config-cache-memcached": "Использовать Memcached (требует дополнительной настройки)", @@ -331,7 +332,7 @@ "config-download-localsettings": "Загрузить LocalSettings.php", "config-help": "справка", "config-help-tooltip": "нажмите, чтобы развернуть", - "config-nofile": "Файл \"$1\" не удается найти. Он был удален?", + "config-nofile": "Файл \"$1\" не удаётся найти. Он был удалён?", "config-extension-link": "Знаете ли вы, что ваш вики-проект поддерживает [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions расширения]?\n\nВы можете просмотреть [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category расширения по категориям]", "config-skins-screenshots": "$1 (скриншоты: $2)", "config-extensions-requires": "$1 (требуется $2)", diff --git a/includes/libs/filebackend/FileBackend.php b/includes/libs/filebackend/FileBackend.php index 19373eaef9..7bc3045c49 100644 --- a/includes/libs/filebackend/FileBackend.php +++ b/includes/libs/filebackend/FileBackend.php @@ -162,9 +162,8 @@ abstract class FileBackend implements LoggerAwareInterface { */ public function __construct( array $config ) { $this->name = $config['name']; - $this->domainId = isset( $config['domainId'] ) - ? $config['domainId'] // e.g. "my_wiki-en_" - : $config['wikiId']; // b/c alias + $this->domainId = $config['domainId'] // e.g. "my_wiki-en_" + ?? $config['wikiId']; // b/c alias if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." ); } elseif ( !is_string( $this->domainId ) ) { diff --git a/includes/libs/filebackend/FileBackendMultiWrite.php b/includes/libs/filebackend/FileBackendMultiWrite.php index 655a71048c..9524155d6b 100644 --- a/includes/libs/filebackend/FileBackendMultiWrite.php +++ b/includes/libs/filebackend/FileBackendMultiWrite.php @@ -87,9 +87,6 @@ class FileBackendMultiWrite extends FileBackend { * This will apply such updates post-send for web requests. Note that * any checks from "syncChecks" are still synchronous. * - * Bogus warning - * @suppress PhanAccessMethodProtected - * * @param array $config * @throws FileBackendError */ diff --git a/includes/libs/objectcache/APCBagOStuff.php b/includes/libs/objectcache/APCBagOStuff.php index 74dcddff02..902cd6a0f8 100644 --- a/includes/libs/objectcache/APCBagOStuff.php +++ b/includes/libs/objectcache/APCBagOStuff.php @@ -72,11 +72,7 @@ class APCBagOStuff extends BagOStuff { } } - protected function doGet( $key, $flags = 0 ) { - return $this->unserialize( apc_fetch( $key . self::KEY_SUFFIX ) ); - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $casToken = null; $blob = apc_fetch( $key . self::KEY_SUFFIX ); @@ -120,10 +116,6 @@ class APCBagOStuff extends BagOStuff { return apc_dec( $key . self::KEY_SUFFIX, $value ); } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); - } - protected function serialize( $value ) { if ( !$this->nativeSerialize && !$this->isInteger( $value ) ) { $value = serialize( $value ); diff --git a/includes/libs/objectcache/APCUBagOStuff.php b/includes/libs/objectcache/APCUBagOStuff.php index 55e940503d..da6544b3d1 100644 --- a/includes/libs/objectcache/APCUBagOStuff.php +++ b/includes/libs/objectcache/APCUBagOStuff.php @@ -39,11 +39,7 @@ class APCUBagOStuff extends APCBagOStuff { parent::__construct( $params ); } - protected function doGet( $key, $flags = 0 ) { - return $this->unserialize( apcu_fetch( $key . self::KEY_SUFFIX ) ); - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $casToken = null; $blob = apcu_fetch( $key . self::KEY_SUFFIX ); @@ -104,8 +100,4 @@ class APCUBagOStuff extends APCBagOStuff { return false; } } - - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); - } } diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index e2b021293a..5669366595 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -175,13 +175,9 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * * @param string $key * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] - * @param int|null $oldFlags [unused] * @return mixed Returns false on failure or if the item does not exist */ - public function get( $key, $flags = 0, $oldFlags = null ) { - // B/C for ( $key, &$casToken = null, $flags = 0 ) - $flags = is_int( $oldFlags ) ? $oldFlags : $flags; - + public function get( $key, $flags = 0 ) { $this->trackDuplicateKeys( $key ); return $this->doGet( $key, $flags ); @@ -223,22 +219,10 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { /** * @param string $key * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] + * @param mixed|null &$casToken Token to use for check-and-set comparisons * @return mixed Returns false on failure or if the item does not exist */ - abstract protected function doGet( $key, $flags = 0 ); - - /** - * @note This method is only needed if merge() uses mergeViaCas() - * - * @param string $key - * @param mixed &$casToken - * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] - * @return mixed Returns false on failure or if the item does not exist - * @throws Exception - */ - protected function getWithToken( $key, &$casToken, $flags = 0 ) { - throw new Exception( __METHOD__ . ' not implemented.' ); - } + abstract protected function doGet( $key, $flags = 0, &$casToken = null ); /** * Set an item @@ -260,6 +244,17 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { */ abstract public function delete( $key, $flags = 0 ); + /** + * Insert an item if it does not already exist + * + * @param string $key + * @param mixed $value + * @param int $exptime + * @param int $flags Bitfield of BagOStuff::WRITE_* constants (since 1.33) + * @return bool Success + */ + abstract public function add( $key, $value, $exptime = 0, $flags = 0 ); + /** * Merge changes into the existing cache value (possibly creating a new one) * @@ -278,7 +273,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * @throws InvalidArgumentException */ public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags ); + return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); } /** @@ -297,7 +292,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { $reportDupes = $this->reportDupes; $this->reportDupes = false; $casToken = null; // passed by reference - $currentValue = $this->getWithToken( $key, $casToken, self::READ_LATEST ); + $currentValue = $this->doGet( $key, self::READ_LATEST, $casToken ); $this->reportDupes = $reportDupes; if ( $this->getLastError() ) { @@ -311,11 +306,13 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { // Derive the new value from the old value $value = call_user_func( $callback, $this, $key, $currentValue, $exptime ); + $hadNoCurrentValue = ( $currentValue === false ); + unset( $currentValue ); // free RAM in case the value is large $this->clearLastError(); if ( $value === false ) { $success = true; // do nothing - } elseif ( $currentValue === false ) { + } elseif ( $hadNoCurrentValue ) { // Try to create the key, failing if it gets created in the meantime $success = $this->add( $key, $value, $exptime, $flags ); } else { @@ -352,7 +349,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { } $curCasToken = null; // passed by reference - $this->getWithToken( $key, $curCasToken, self::READ_LATEST ); + $this->doGet( $key, self::READ_LATEST, $curCasToken ); if ( $casToken === $curCasToken ) { $success = $this->set( $key, $value, $exptime, $flags ); } else { @@ -369,58 +366,6 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { return $success; } - /** - * @see BagOStuff::merge() - * - * @param string $key - * @param callable $callback Callback method to be executed - * @param int $exptime Either an interval in seconds or a unix timestamp for expiry - * @param int $attempts The amount of times to attempt a merge in case of failure - * @param int $flags Bitfield of BagOStuff::WRITE_* constants - * @return bool Success - */ - protected function mergeViaLock( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - if ( $attempts <= 1 ) { - $timeout = 0; // clearly intended to be "non-blocking" - } else { - $timeout = 3; - } - - if ( !$this->lock( $key, $timeout ) ) { - return false; - } - - $this->clearLastError(); - $reportDupes = $this->reportDupes; - $this->reportDupes = false; - $currentValue = $this->get( $key, self::READ_LATEST ); - $this->reportDupes = $reportDupes; - - if ( $this->getLastError() ) { - $this->logger->warning( - __METHOD__ . ' failed due to I/O error on get() for {key}.', - [ 'key' => $key ] - ); - - $success = false; - } else { - // Derive the new value from the old value - $value = call_user_func( $callback, $this, $key, $currentValue, $exptime ); - if ( $value === false ) { - $success = true; // do nothing - } else { - $success = $this->set( $key, $value, $exptime, $flags ); // set the new value - } - } - - if ( !$this->unlock( $key ) ) { - // this should never happen - trigger_error( "Could not release lock for key '$key'." ); - } - - return $success; - } - /** * Change the expiration on a key if it exists * @@ -637,16 +582,6 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { return $res; } - /** - * Insertion - * @param string $key - * @param mixed $value - * @param int $exptime - * @param int $flags Bitfield of BagOStuff::WRITE_* constants (since 1.33) - * @return bool Success - */ - abstract public function add( $key, $value, $exptime = 0, $flags = 0 ); - /** * Increase stored value of $key by $value while preserving its TTL * @param string $key Key to increase @@ -753,13 +688,21 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { } } + /** + * @param int $exptime + * @return bool + */ + protected function expiryIsRelative( $exptime ) { + return ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ); + } + /** * Convert an optionally relative time to an absolute time * @param int $exptime * @return int */ - protected function convertExpiry( $exptime ) { - if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) { + protected function convertToExpiry( $exptime ) { + if ( $this->expiryIsRelative( $exptime ) ) { return (int)$this->getCurrentTime() + $exptime; } else { return $exptime; diff --git a/includes/libs/objectcache/CachedBagOStuff.php b/includes/libs/objectcache/CachedBagOStuff.php index 95dda71f2a..8892f73899 100644 --- a/includes/libs/objectcache/CachedBagOStuff.php +++ b/includes/libs/objectcache/CachedBagOStuff.php @@ -32,6 +32,7 @@ * up going to the HashBagOStuff used for the in-memory cache). * * @ingroup Cache + * @TODO: Make this class use composition instead of calling super */ class CachedBagOStuff extends HashBagOStuff { /** @var BagOStuff */ @@ -50,10 +51,10 @@ class CachedBagOStuff extends HashBagOStuff { $this->attrMap = $backend->attrMap; } - protected function doGet( $key, $flags = 0 ) { - $ret = parent::doGet( $key, $flags ); + public function get( $key, $flags = 0 ) { + $ret = parent::get( $key, $flags ); if ( $ret === false && !$this->hasKey( $key ) ) { - $ret = $this->backend->doGet( $key, $flags ); + $ret = $this->backend->get( $key, $flags ); $this->set( $key, $ret, 0, self::WRITE_CACHE_ONLY ); } return $ret; diff --git a/includes/libs/objectcache/EmptyBagOStuff.php b/includes/libs/objectcache/EmptyBagOStuff.php index e0f4364228..ffe3a4c53e 100644 --- a/includes/libs/objectcache/EmptyBagOStuff.php +++ b/includes/libs/objectcache/EmptyBagOStuff.php @@ -27,7 +27,9 @@ * @ingroup Cache */ class EmptyBagOStuff extends BagOStuff { - protected function doGet( $key, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { + $casToken = null; + return false; } diff --git a/includes/libs/objectcache/HashBagOStuff.php b/includes/libs/objectcache/HashBagOStuff.php index 3c6520e1b3..d24f40854e 100644 --- a/includes/libs/objectcache/HashBagOStuff.php +++ b/includes/libs/objectcache/HashBagOStuff.php @@ -58,12 +58,10 @@ class HashBagOStuff extends BagOStuff { } } - protected function doGet( $key, $flags = 0 ) { - if ( !$this->hasKey( $key ) ) { - return false; - } + protected function doGet( $key, $flags = 0, &$casToken = null ) { + $casToken = null; - if ( $this->expire( $key ) ) { + if ( !$this->hasKey( $key ) || $this->expire( $key ) ) { return false; } @@ -72,18 +70,9 @@ class HashBagOStuff extends BagOStuff { unset( $this->bag[$key] ); $this->bag[$key] = $temp; - return $this->bag[$key][self::KEY_VAL]; - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { - $casToken = null; - - $value = $this->doGet( $key ); - if ( $value !== false ) { - $casToken = $this->bag[$key][self::KEY_CAS]; - } + $casToken = $this->bag[$key][self::KEY_CAS]; - return $value; + return $this->bag[$key][self::KEY_VAL]; } public function set( $key, $value, $exptime = 0, $flags = 0 ) { @@ -91,7 +80,7 @@ class HashBagOStuff extends BagOStuff { unset( $this->bag[$key] ); $this->bag[$key] = [ self::KEY_VAL => $value, - self::KEY_EXP => $this->convertExpiry( $exptime ), + self::KEY_EXP => $this->convertToExpiry( $exptime ), self::KEY_CAS => $this->token . ':' . ++self::$casCounter ]; @@ -130,10 +119,6 @@ class HashBagOStuff extends BagOStuff { return false; } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); - } - /** * Clear all values in cache */ diff --git a/includes/libs/objectcache/IExpiringStore.php b/includes/libs/objectcache/IExpiringStore.php index c1edff794d..61a4c618cf 100644 --- a/includes/libs/objectcache/IExpiringStore.php +++ b/includes/libs/objectcache/IExpiringStore.php @@ -21,7 +21,7 @@ */ /** - * Generic base class for storage interfaces. + * Generic interface for lightweight expiring object stores. * * Provides convenient TTL constants. * @@ -44,16 +44,19 @@ interface IExpiringStore { const TTL_INDEFINITE = 0; - // Attribute and QoS constants; higher QOS values with the same prefix rank higher... - // Medium attributes constants related to emulation or media type + // Emulation/fallback medium attribute (e.g. SQLBagOStuff) const ATTR_EMULATION = 1; + // Quality of service constants for ATTR_EMULATION (higher means faster) const QOS_EMULATION_SQL = 1; - // Medium attributes constants related to replica consistency - const ATTR_SYNCWRITES = 2; // SYNC_WRITES flag support + + // Replica synchronization/consistency attribute of medium when SYNC_WRITES is used + const ATTR_SYNCWRITES = 2; + // Quality of service constants for ATTR_SYNCWRITES (higher means more consistent) const QOS_SYNCWRITES_NONE = 1; // replication only supports eventual consistency or less const QOS_SYNCWRITES_BE = 2; // best effort synchronous with limited retries const QOS_SYNCWRITES_QC = 3; // write quorum applied directly to state machines where R+W > N const QOS_SYNCWRITES_SS = 4; // strict-serializable, nodes refuse reads if possible stale + // Generic "unknown" value that is useful for comparisons (e.g. always good enough) const QOS_UNKNOWN = INF; diff --git a/includes/libs/objectcache/MemcachedBagOStuff.php b/includes/libs/objectcache/MemcachedBagOStuff.php index 5453862cf2..71e3331708 100644 --- a/includes/libs/objectcache/MemcachedBagOStuff.php +++ b/includes/libs/objectcache/MemcachedBagOStuff.php @@ -50,13 +50,7 @@ class MemcachedBagOStuff extends BagOStuff { ]; } - protected function doGet( $key, $flags = 0 ) { - $casToken = null; - - return $this->getWithToken( $key, $casToken, $flags ); - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { return $this->client->get( $this->validateKeyEncoding( $key ), $casToken ); } @@ -91,10 +85,6 @@ class MemcachedBagOStuff extends BagOStuff { return ( $n !== false && $n !== null ) ? $n : false; } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts ); - } - public function changeTTL( $key, $exptime = 0, $flags = 0 ) { return $this->client->touch( $this->validateKeyEncoding( $key ), $this->fixExpiry( $exptime ) ); diff --git a/includes/libs/objectcache/MemcachedPeclBagOStuff.php b/includes/libs/objectcache/MemcachedPeclBagOStuff.php index 62a57b6c36..489f00102e 100644 --- a/includes/libs/objectcache/MemcachedPeclBagOStuff.php +++ b/includes/libs/objectcache/MemcachedPeclBagOStuff.php @@ -138,7 +138,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff { return $params; } - protected function getWithToken( $key, &$casToken, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $this->debugLog( "get($key)" ); if ( defined( Memcached::class . '::GET_EXTENDED' ) ) { // v3.0.0 $flags = Memcached::GET_EXTENDED; diff --git a/includes/libs/objectcache/MultiWriteBagOStuff.php b/includes/libs/objectcache/MultiWriteBagOStuff.php index 5cf9de4cc8..adb9bb8ae4 100644 --- a/includes/libs/objectcache/MultiWriteBagOStuff.php +++ b/includes/libs/objectcache/MultiWriteBagOStuff.php @@ -27,6 +27,9 @@ use Wikimedia\ObjectFactory; * are implemented by reading from the caches in the order they are given in * the configuration until a cache gives a positive result. * + * Note that cache key construction will use the first cache backend in the list, + * so make sure that the other backends can handle such keys (e.g. via encoding). + * * @ingroup Cache */ class MultiWriteBagOStuff extends BagOStuff { @@ -100,11 +103,11 @@ class MultiWriteBagOStuff extends BagOStuff { } } - protected function doGet( $key, $flags = 0 ) { + public function get( $key, $flags = 0 ) { if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) { // If the latest write was a delete(), we do NOT want to fallback // to the other tiers and possibly see the old value. Also, this - // is used by mergeViaLock(), which only needs to hit the primary. + // is used by merge(), which only needs to hit the primary. return $this->caches[0]->get( $key, $flags ); } @@ -124,67 +127,72 @@ class MultiWriteBagOStuff extends BagOStuff { ) { // Backfill the value to the higher (and often faster/smaller) cache tiers $this->doWrite( - $missIndexes, $this->asyncWrites, 'set', $key, $value, self::UPGRADE_TTL + $missIndexes, + $this->asyncWrites, + 'set', + [ $key, $value, self::UPGRADE_TTL ] ); } return $value; } - public function set( $key, $value, $exptime = 0, $flags = 0 ) { - $asyncWrites = ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) - ? false - : $this->asyncWrites; - - return $this->doWrite( $this->cacheIndexes, $asyncWrites, 'set', $key, $value, $exptime ); + return $this->doWrite( + $this->cacheIndexes, + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); } public function delete( $key, $flags = 0 ) { - return $this->doWrite( $this->cacheIndexes, $this->asyncWrites, 'delete', $key ); + return $this->doWrite( + $this->cacheIndexes, + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); } public function add( $key, $value, $exptime = 0, $flags = 0 ) { // Try the write to the top-tier cache - $ok = $this->doWrite( [ 0 ], $this->asyncWrites, 'add', $key, $value, $exptime ); + $ok = $this->doWrite( + [ 0 ], + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); + if ( $ok ) { // Relay the add() using set() if it succeeded. This is meant to handle certain // migration scenarios where the same store might get written to twice for certain // keys. In that case, it does not make sense to return false due to "self-conflicts". return $this->doWrite( array_slice( $this->cacheIndexes, 1 ), - $this->asyncWrites, + $this->usesAsyncWritesGivenFlags( $flags ), 'set', - $key, - $value, - $exptime + [ $key, $value, $exptime, $flags ] ); } return false; } - public function incr( $key, $value = 1 ) { - return $this->doWrite( $this->cacheIndexes, $this->asyncWrites, 'incr', $key, $value ); - } - - public function decr( $key, $value = 1 ) { - return $this->doWrite( $this->cacheIndexes, $this->asyncWrites, 'decr', $key, $value ); - } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - $asyncWrites = ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) - ? false - : $this->asyncWrites; + return $this->doWrite( + $this->cacheIndexes, + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); + } + public function changeTTL( $key, $exptime = 0, $flags = 0 ) { return $this->doWrite( $this->cacheIndexes, - $asyncWrites, - 'merge', - $key, - $callback, - $exptime, - $attempts, - $flags + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() ); } @@ -197,6 +205,82 @@ class MultiWriteBagOStuff extends BagOStuff { // Only the first cache is locked return $this->caches[0]->unlock( $key ); } + /** + * Delete objects expiring before a certain date. + * + * Succeed if any of the child caches succeed. + * @param string $date + * @param bool|callable $progressCallback + * @return bool + */ + public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) { + $ret = false; + foreach ( $this->caches as $cache ) { + if ( $cache->deleteObjectsExpiringBefore( $date, $progressCallback ) ) { + $ret = true; + } + } + + return $ret; + } + + public function getMulti( array $keys, $flags = 0 ) { + // Just iterate over each key in order to handle all the backfill logic + $res = []; + foreach ( $keys as $key ) { + $val = $this->get( $key, $flags ); + if ( $val !== false ) { + $res[$key] = $val; + } + } + + return $res; + } + + public function setMulti( array $data, $exptime = 0, $flags = 0 ) { + return $this->doWrite( + $this->cacheIndexes, + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); + } + + public function deleteMulti( array $data, $flags = 0 ) { + return $this->doWrite( + $this->cacheIndexes, + $this->usesAsyncWritesGivenFlags( $flags ), + __FUNCTION__, + func_get_args() + ); + } + + public function incr( $key, $value = 1 ) { + return $this->doWrite( + $this->cacheIndexes, + $this->asyncWrites, + __FUNCTION__, + func_get_args() + ); + } + + public function decr( $key, $value = 1 ) { + return $this->doWrite( + $this->cacheIndexes, + $this->asyncWrites, + __FUNCTION__, + func_get_args() + ); + } + + public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) { + return $this->doWrite( + $this->cacheIndexes, + $this->asyncWrites, + __FUNCTION__, + func_get_args() + ); + } public function getLastError() { return $this->caches[0]->getLastError(); @@ -205,19 +289,17 @@ class MultiWriteBagOStuff extends BagOStuff { public function clearLastError() { $this->caches[0]->clearLastError(); } - /** * Apply a write method to the backing caches specified by $indexes (in order) * * @param int[] $indexes List of backing cache indexes * @param bool $asyncWrites - * @param string $method - * @param mixed $args,... + * @param string $method Method name of backing caches + * @param array[] $args Arguments to the method of backing caches * @return bool */ - protected function doWrite( $indexes, $asyncWrites, $method /*, ... */ ) { + protected function doWrite( $indexes, $asyncWrites, $method, array $args ) { $ret = true; - $args = array_slice( func_get_args(), 3 ); if ( array_diff( $indexes, [ 0 ] ) && $asyncWrites && $method !== 'merge' ) { // Deep-clone $args to prevent misbehavior when something writes an @@ -249,22 +331,11 @@ class MultiWriteBagOStuff extends BagOStuff { } /** - * Delete objects expiring before a certain date. - * - * Succeed if any of the child caches succeed. - * @param string $date - * @param bool|callable $progressCallback + * @param int $flags * @return bool */ - public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) { - $ret = false; - foreach ( $this->caches as $cache ) { - if ( $cache->deleteObjectsExpiringBefore( $date, $progressCallback ) ) { - $ret = true; - } - } - - return $ret; + protected function usesAsyncWritesGivenFlags( $flags ) { + return ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) ? false : $this->asyncWrites; } public function makeKeyInternal( $keyspace, $args ) { @@ -278,4 +349,8 @@ class MultiWriteBagOStuff extends BagOStuff { public function makeGlobalKey( $class, $component = null ) { return $this->caches[0]->makeGlobalKey( ...func_get_args() ); } + + protected function doGet( $key, $flags = 0, &$casToken = null ) { + throw new LogicException( __METHOD__ . ': proxy class does not need this method.' ); + } } diff --git a/includes/libs/objectcache/RESTBagOStuff.php b/includes/libs/objectcache/RESTBagOStuff.php index c127ec6910..4d8ae596d9 100644 --- a/includes/libs/objectcache/RESTBagOStuff.php +++ b/includes/libs/objectcache/RESTBagOStuff.php @@ -84,12 +84,9 @@ class RESTBagOStuff extends BagOStuff { $this->client->setLogger( $logger ); } - /** - * @param string $key - * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] - * @return mixed Returns false on failure and if the item does not exist - */ - protected function doGet( $key, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { + $casToken = null; + $req = [ 'method' => 'GET', 'url' => $this->url . rawurlencode( $key ), @@ -98,7 +95,11 @@ class RESTBagOStuff extends BagOStuff { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->client->run( $req ); if ( $rcode === 200 ) { if ( is_string( $rbody ) ) { - return unserialize( $rbody ); + $value = unserialize( $rbody ); + /// @FIXME: use some kind of hash or UUID header as CAS token + $casToken = ( $value !== false ) ? $rbody : null; + + return $value; } return false; } @@ -108,22 +109,6 @@ class RESTBagOStuff extends BagOStuff { return false; } - /** - * Handle storage error - * @param string $msg Error message - * @param int $rcode Error code from client - * @param string $rerr Error message from client - * @return false - */ - protected function handleError( $msg, $rcode, $rerr ) { - $this->logger->error( "$msg : ({code}) {error}", [ - 'code' => $rcode, - 'error' => $rerr - ] ); - $this->setLastError( $rcode === 0 ? self::ERR_UNREACHABLE : self::ERR_UNEXPECTED ); - return false; - } - public function set( $key, $value, $exptime = 0, $flags = 0 ) { // @TODO: respect WRITE_SYNC (e.g. EACH_QUORUM) // @TODO: respect $exptime @@ -172,4 +157,20 @@ class RESTBagOStuff extends BagOStuff { return false; } + + /** + * Handle storage error + * @param string $msg Error message + * @param int $rcode Error code from client + * @param string $rerr Error message from client + * @return false + */ + protected function handleError( $msg, $rcode, $rerr ) { + $this->logger->error( "$msg : ({code}) {error}", [ + 'code' => $rcode, + 'error' => $rerr + ] ); + $this->setLastError( $rcode === 0 ? self::ERR_UNREACHABLE : self::ERR_UNEXPECTED ); + return false; + } } diff --git a/includes/libs/objectcache/RedisBagOStuff.php b/includes/libs/objectcache/RedisBagOStuff.php index f64fe7e780..2c74d45916 100644 --- a/includes/libs/objectcache/RedisBagOStuff.php +++ b/includes/libs/objectcache/RedisBagOStuff.php @@ -86,13 +86,9 @@ class RedisBagOStuff extends BagOStuff { $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE; } - protected function doGet( $key, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $casToken = null; - return $this->getWithToken( $key, $casToken, $flags ); - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { list( $server, $conn ) = $this->getConnection( $key ); if ( !$conn ) { return false; @@ -297,10 +293,6 @@ class RedisBagOStuff extends BagOStuff { return $result; } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts ); - } - /** * Non-atomic implementation of incr(). * @@ -333,21 +325,29 @@ class RedisBagOStuff extends BagOStuff { return $result; } - public function changeTTL( $key, $expiry = 0, $flags = 0 ) { + public function changeTTL( $key, $exptime = 0, $flags = 0 ) { list( $server, $conn ) = $this->getConnection( $key ); if ( !$conn ) { return false; } - $expiry = $this->convertToRelative( $expiry ); + $relative = $this->expiryIsRelative( $exptime ); try { - $result = $conn->expire( $key, $expiry ); + if ( $exptime == 0 ) { + $result = $conn->persist( $key ); + $this->logRequest( 'persist', $key, $server, $result ); + } elseif ( $relative ) { + $result = $conn->expire( $key, $this->convertToRelative( $exptime ) ); + $this->logRequest( 'expire', $key, $server, $result ); + } else { + $result = $conn->expireAt( $key, $this->convertToExpiry( $exptime ) ); + $this->logRequest( 'expireAt', $key, $server, $result ); + } } catch ( RedisException $e ) { $result = false; $this->handleException( $conn, $e ); } - $this->logRequest( 'expire', $key, $server, $result ); return $result; } diff --git a/includes/libs/objectcache/ReplicatedBagOStuff.php b/includes/libs/objectcache/ReplicatedBagOStuff.php index 14e2fefbd9..70f9096001 100644 --- a/includes/libs/objectcache/ReplicatedBagOStuff.php +++ b/includes/libs/objectcache/ReplicatedBagOStuff.php @@ -74,44 +74,30 @@ class ReplicatedBagOStuff extends BagOStuff { $this->readStore->setDebug( $debug ); } - protected function doGet( $key, $flags = 0 ) { + public function get( $key, $flags = 0 ) { return ( $flags & self::READ_LATEST ) ? $this->writeStore->get( $key, $flags ) : $this->readStore->get( $key, $flags ); } - public function getMulti( array $keys, $flags = 0 ) { - return ( $flags & self::READ_LATEST ) - ? $this->writeStore->getMulti( $keys, $flags ) - : $this->readStore->getMulti( $keys, $flags ); - } - public function set( $key, $value, $exptime = 0, $flags = 0 ) { return $this->writeStore->set( $key, $value, $exptime, $flags ); } - public function setMulti( array $keys, $exptime = 0, $flags = 0 ) { - return $this->writeStore->setMulti( $keys, $exptime, $flags ); - } - public function delete( $key, $flags = 0 ) { return $this->writeStore->delete( $key, $flags ); } - public function deleteMulti( array $keys, $flags = 0 ) { - return $this->writeStore->deleteMulti( $keys, $flags ); - } - public function add( $key, $value, $exptime = 0, $flags = 0 ) { return $this->writeStore->add( $key, $value, $exptime, $flags ); } - public function incr( $key, $value = 1 ) { - return $this->writeStore->incr( $key, $value ); + public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { + return $this->writeStore->merge( $key, $callback, $exptime, $attempts, $flags ); } - public function decr( $key, $value = 1 ) { - return $this->writeStore->decr( $key, $value ); + public function changeTTL( $key, $exptime = 0, $flags = 0 ) { + return $this->writeStore->changeTTL( $key, $exptime, $flags ); } public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) { @@ -122,8 +108,34 @@ class ReplicatedBagOStuff extends BagOStuff { return $this->writeStore->unlock( $key ); } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->writeStore->merge( $key, $callback, $exptime, $attempts, $flags ); + public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) { + return $this->writeStore->deleteObjectsExpiringBefore( $date, $progressCallback ); + } + + public function getMulti( array $keys, $flags = 0 ) { + return ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) + ? $this->writeStore->getMulti( $keys, $flags ) + : $this->readStore->getMulti( $keys, $flags ); + } + + public function setMulti( array $data, $exptime = 0, $flags = 0 ) { + return $this->writeStore->setMulti( $data, $exptime, $flags ); + } + + public function deleteMulti( array $keys, $flags = 0 ) { + return $this->writeStore->deleteMulti( $keys, $flags ); + } + + public function incr( $key, $value = 1 ) { + return $this->writeStore->incr( $key, $value ); + } + + public function decr( $key, $value = 1 ) { + return $this->writeStore->decr( $key, $value ); + } + + public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) { + return $this->writeStore->incrWithInit( $key, $ttl, $value, $init ); } public function getLastError() { @@ -148,4 +160,8 @@ class ReplicatedBagOStuff extends BagOStuff { public function makeGlobalKey( $class, $component = null ) { return $this->writeStore->makeGlobalKey( ...func_get_args() ); } + + protected function doGet( $key, $flags = 0, &$casToken = null ) { + throw new LogicException( __METHOD__ . ': proxy class does not need this method.' ); + } } diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index ba21156047..87bccc5208 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -1150,10 +1150,17 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * It is generally preferable to use a class constant when setting this value. * This has no effect unless pcTTL is used. * Default: WANObjectCache::PC_PRIMARY. - * - version: Integer version number. This allows for callers to make breaking changes to - * how values are stored while maintaining compatability and correct cache purges. New - * versions are stored alongside older versions concurrently. Avoid storing class objects - * however, as this reduces compatibility (due to serialization). + * - version: Integer version number. This lets callers make breaking changes to the format + * of cached values without causing problems for sites that use non-instantaneous code + * deployments. Old and new code will recognize incompatible versions and purges from + * both old and new code will been seen by each other. When this method encounters an + * incompatibly versioned value at the provided key, a "variant key" will be used for + * reading from and saving to cache. The variant key is specific to the key and version + * number provided to this method. If the variant key value is older than that of the + * provided key, or the provided key is non-existant, then the variant key will be seen + * as non-existant. Therefore, delete() calls invalidate the provided key's variant keys. + * The "checkKeys" and "touchedCallback" options still apply to variant keys as usual. + * Avoid storing class objects, as this reduces compatibility (due to serialization). * Default: null. * - minAsOf: Reject values if they were generated before this UNIX timestamp. * This is useful if the source of a key is suspected of having possibly changed diff --git a/includes/libs/objectcache/WinCacheBagOStuff.php b/includes/libs/objectcache/WinCacheBagOStuff.php index 0ca3e4a5f9..818f6f1dc2 100644 --- a/includes/libs/objectcache/WinCacheBagOStuff.php +++ b/includes/libs/objectcache/WinCacheBagOStuff.php @@ -28,13 +28,7 @@ * @ingroup Cache */ class WinCacheBagOStuff extends BagOStuff { - protected function doGet( $key, $flags = 0 ) { - $blob = wincache_ucache_get( $key ); - - return is_string( $blob ) ? unserialize( $blob ) : false; - } - - protected function getWithToken( $key, &$casToken, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $casToken = null; $blob = wincache_ucache_get( $key ); @@ -43,12 +37,10 @@ class WinCacheBagOStuff extends BagOStuff { } $value = unserialize( $blob ); - if ( $value === false ) { - return false; + if ( $value !== false ) { + $casToken = (string)$blob; // don't bother hashing this } - $casToken = $blob; // don't bother hashing this - return $value; } @@ -58,7 +50,7 @@ class WinCacheBagOStuff extends BagOStuff { } $curCasToken = null; // passed by reference - $this->getWithToken( $key, $curCasToken, self::READ_LATEST ); + $this->doGet( $key, self::READ_LATEST, $curCasToken ); if ( $casToken === $curCasToken ) { $success = $this->set( $key, $value, $exptime, $flags ); } else { @@ -93,10 +85,6 @@ class WinCacheBagOStuff extends BagOStuff { return true; } - public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); - } - /** * Construct a cache key. * diff --git a/includes/libs/rdbms/database/DBConnRef.php b/includes/libs/rdbms/database/DBConnRef.php index 70b05835c1..cf582b7e99 100644 --- a/includes/libs/rdbms/database/DBConnRef.php +++ b/includes/libs/rdbms/database/DBConnRef.php @@ -254,7 +254,7 @@ class DBConnRef implements IDatabase { throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' ); } - public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { + public function query( $sql, $fname = __METHOD__, $flags = 0 ) { return $this->__call( __FUNCTION__, func_get_args() ); } diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index 9ce3086c38..ae4b71ab9f 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -61,8 +61,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware const SLOW_WRITE_SEC = 0.500; const SMALL_WRITE_ROWS = 100; - /** @var string Whether lock granularity is on the level of the entire database */ + /** @var string Lock granularity is on the level of the entire database */ const ATTR_DB_LEVEL_LOCKING = 'db-level-locking'; + /** @var string The SCHEMA keyword refers to a grouping of tables in a database */ + const ATTR_SCHEMAS_AS_TABLE_GROUPS = 'supports-schemas'; /** @var int New Database instance will not be connected yet when returned */ const NEW_UNCONNECTED = 0; @@ -280,6 +282,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** @var int No transaction is active */ const STATUS_TRX_NONE = 3; + /** @var int Writes to this temporary table do not affect lastDoneWrites() */ + const TEMP_NORMAL = 1; + /** @var int Writes to this temporary table effect lastDoneWrites() */ + const TEMP_PSEUDO_PERMANENT = 2; + /** * @note exceptions for missing libraries/drivers should be thrown in initConnection() * @param array $params Parameters passed from Database::factory() @@ -475,12 +482,15 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** * @param string $dbType A possible DB type (sqlite, mysql, postgres,...) * @param string|null $driver Optional name of a specific DB client driver - * @return array Map of (Database::ATTRIBUTE_* constant => value) for all such constants + * @return array Map of (Database::ATTR_* constant => value) for all such constants * @throws InvalidArgumentException * @since 1.31 */ final public static function attributesFromType( $dbType, $driver = null ) { - static $defaults = [ self::ATTR_DB_LEVEL_LOCKING => false ]; + static $defaults = [ + self::ATTR_DB_LEVEL_LOCKING => false, + self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false + ]; $class = self::getClass( $dbType, $driver ); @@ -544,7 +554,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } /** - * @return array Map of (Database::ATTRIBUTE_* constant => value + * @return array Map of (Database::ATTR_* constant => value * @since 1.31 */ protected static function getAttributes() { @@ -1135,47 +1145,55 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** * @param string $sql A SQL query - * @return bool Whether $sql is SQL for TEMPORARY table operation + * @param bool $pseudoPermanent Treat any table from CREATE TEMPORARY as pseudo-permanent + * @return int|null A self::TEMP_* constant for temp table operations or null otherwise */ - protected function registerTempTableOperation( $sql ) { + protected function registerTempTableWrite( $sql, $pseudoPermanent ) { + static $qt = '[`"\']?(\w+)[`"\']?'; // quoted table + if ( preg_match( - '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', + '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?' . $qt . '/i', $sql, $matches ) ) { - $this->sessionTempTables[$matches[1]] = 1; + $type = $pseudoPermanent ? self::TEMP_PSEUDO_PERMANENT : self::TEMP_NORMAL; + $this->sessionTempTables[$matches[1]] = $type; - return true; + return $type; } elseif ( preg_match( - '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', + '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i', $sql, $matches ) ) { - $isTemp = isset( $this->sessionTempTables[$matches[1]] ); + $type = $this->sessionTempTables[$matches[1]] ?? null; unset( $this->sessionTempTables[$matches[1]] ); - return $isTemp; + return $type; } elseif ( preg_match( - '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', + '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i', $sql, $matches ) ) { - return isset( $this->sessionTempTables[$matches[1]] ); + return $this->sessionTempTables[$matches[1]] ?? null; } elseif ( preg_match( - '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i', + '/^(?:(?:INSERT|REPLACE)\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+' . $qt . '/i', $sql, $matches ) ) { - return isset( $this->sessionTempTables[$matches[1]] ); + return $this->sessionTempTables[$matches[1]] ?? null; } - return false; + return null; } - public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { + public function query( $sql, $fname = __METHOD__, $flags = 0 ) { $this->assertTransactionStatus( $sql, $fname ); $this->assertHasConnectionHandle(); + $flags = (int)$flags; // b/c; this field used to be a bool + $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS ); + $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT ); + $priorTransaction = $this->trxLevel; $priorWritesPending = $this->writesOrCallbacksPending(); $this->lastQuery = $sql; @@ -1184,8 +1202,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware # In theory, non-persistent writes are allowed in read-only mode, but due to things # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway... $this->assertIsWritableMaster(); - # Avoid treating temporary table operations as meaningful "writes" - $isEffectiveWrite = !$this->registerTempTableOperation( $sql ); + # Do not treat temporary table writes as "meaningful writes" that need committing. + # Profile them as reads. Integration tests can override this behavior via $flags. + $tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent ); + $isEffectiveWrite = ( $tableType !== self::TEMP_NORMAL ); } else { $isEffectiveWrite = false; } @@ -1240,12 +1260,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->trxStatus = self::STATUS_TRX_ERROR; $this->trxStatusCause = $this->getQueryExceptionAndLog( $lastError, $lastErrno, $sql, $fname ); - $tempIgnore = false; // cannot recover + $ignoreErrors = false; // cannot recover $this->trxStatusIgnoredCause = null; } } - $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore ); + $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $ignoreErrors ); } return $this->resultObject( $ret ); @@ -1514,17 +1534,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** * Report a query error. Log the error, and if neither the object ignore - * flag nor the $tempIgnore flag is set, throw a DBQueryError. + * flag nor the $ignoreErrors flag is set, throw a DBQueryError. * * @param string $error * @param int $errno * @param string $sql * @param string $fname - * @param bool $tempIgnore + * @param bool $ignoreErrors * @throws DBQueryError */ - public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - if ( $tempIgnore ) { + public function reportQueryError( $error, $errno, $sql, $fname, $ignoreErrors = false ) { + if ( $ignoreErrors ) { $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" ); } else { $exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname ); @@ -2532,7 +2552,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware throw new InvalidArgumentException( "Table must be a string or Subquery." ); } - if ( !strlen( $alias ) || $alias === $table ) { + if ( $alias === false || $alias === $table ) { if ( $table instanceof Subquery ) { throw new InvalidArgumentException( "Subquery table missing alias." ); } @@ -4684,6 +4704,15 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->indexAliases = $aliases; } + /** + * @param int $field + * @param int $flags + * @return bool + */ + protected function hasFlags( $field, $flags ) { + return ( ( $field & $flags ) === $flags ); + } + /** * Get the underlying binding connection handle * diff --git a/includes/libs/rdbms/database/DatabaseDomain.php b/includes/libs/rdbms/database/DatabaseDomain.php index 7559b06c3b..8d8285426a 100644 --- a/includes/libs/rdbms/database/DatabaseDomain.php +++ b/includes/libs/rdbms/database/DatabaseDomain.php @@ -43,15 +43,17 @@ class DatabaseDomain { */ public function __construct( $database, $schema, $prefix ) { if ( $database !== null && ( !is_string( $database ) || !strlen( $database ) ) ) { - throw new InvalidArgumentException( "Database must be null or a non-empty string." ); + throw new InvalidArgumentException( 'Database must be null or a non-empty string.' ); } $this->database = $database; if ( $schema !== null && ( !is_string( $schema ) || !strlen( $schema ) ) ) { - throw new InvalidArgumentException( "Schema must be null or a non-empty string." ); + throw new InvalidArgumentException( 'Schema must be null or a non-empty string.' ); } $this->schema = $schema; if ( !is_string( $prefix ) ) { - throw new InvalidArgumentException( "Prefix must be a string." ); + throw new InvalidArgumentException( 'Prefix must be a string.' ); + } elseif ( $prefix !== '' && substr( $prefix, -1, 1 ) !== '_' ) { + throw new InvalidArgumentException( 'A non-empty prefix must end with "_".' ); } $this->prefix = $prefix; } @@ -133,7 +135,7 @@ class DatabaseDomain { return true; // even the prefix doesn't matter } - $other = ( $other instanceof self ) ? $other : self::newFromId( $other ); + $other = self::newFromId( $other ); return ( ( $this->database === $other->database || $this->database === null ) && @@ -188,7 +190,7 @@ class DatabaseDomain { * @return string */ private function convertToString() { - $parts = [ $this->database ]; + $parts = [ (string)$this->database ]; if ( $this->schema !== null ) { $parts[] = $this->schema; } diff --git a/includes/libs/rdbms/database/DatabaseMssql.php b/includes/libs/rdbms/database/DatabaseMssql.php index ad5cf98f5c..2aefd5f2a4 100644 --- a/includes/libs/rdbms/database/DatabaseMssql.php +++ b/includes/libs/rdbms/database/DatabaseMssql.php @@ -1328,8 +1328,9 @@ class DatabaseMssql extends Database { /** * @param string $name - * @param string $format - * @return string + * @param string $format One of "quoted" (default), "raw", or "split". + * @return string|array When the requested $format is "split", a list of database, schema, and + * table name is returned. Database and schema can be `false`. */ function tableName( $name, $format = 'quoted' ) { # Replace reserved words with better ones @@ -1344,18 +1345,17 @@ class DatabaseMssql extends Database { /** * call this instead of tableName() in the updater when renaming tables * @param string $name - * @param string $format One of quoted, raw, or split - * @return string + * @param string $format One of "quoted" (default), "raw", or "split". + * @return string|array When the requested $format is "split", a list of database, schema, and + * table name is returned. Database and schema can be `false`. + * @private */ function realTableName( $name, $format = 'quoted' ) { $table = parent::tableName( $name, $format ); if ( $format == 'split' ) { // Used internally, we want the schema split off from the table name and returned // as a list with 3 elements (database, schema, table) - $table = explode( '.', $table ); - while ( count( $table ) < 3 ) { - array_unshift( $table, false ); - } + return array_pad( explode( '.', $table, 3 ), -3, false ); } return $table; } @@ -1412,6 +1412,9 @@ class DatabaseMssql extends Database { return "CAST( $field AS NVARCHAR )"; } + public static function getAttributes() { + return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ]; + } } /** diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 25d78da18d..7fccd57f45 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -225,6 +225,39 @@ abstract class DatabaseMysqlBase extends Database { } } + protected function doSelectDomain( DatabaseDomain $domain ) { + if ( $domain->getSchema() !== null ) { + throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." ); + } + + $database = $domain->getDatabase(); + // A null database means "don't care" so leave it as is and update the table prefix + if ( $database === null ) { + $this->currentDomain = new DatabaseDomain( + $this->currentDomain->getDatabase(), + null, + $domain->getTablePrefix() + ); + + return true; + } + + if ( $database !== $this->getDBname() ) { + $sql = 'USE ' . $this->addIdentifierQuotes( $database ); + $ret = $this->doQuery( $sql ); + if ( $ret === false ) { + $error = $this->lastError(); + $errno = $this->lastErrno(); + $this->reportQueryError( $error, $errno, $sql, __METHOD__ ); + } + } + + // Update that domain fields on success (no exception thrown) + $this->currentDomain = $domain; + + return true; + } + /** * Open a connection to a MySQL server * @@ -1427,7 +1460,7 @@ abstract class DatabaseMysqlBase extends Database { $oldName = $this->addIdentifierQuotes( $oldName ); $query = "CREATE $tmp TABLE $newName (LIKE $oldName)"; - return $this->query( $query, $fname ); + return $this->query( $query, $fname, $this::QUERY_PSEUDO_PERMANENT ); } /** diff --git a/includes/libs/rdbms/database/DatabaseMysqli.php b/includes/libs/rdbms/database/DatabaseMysqli.php index 1c4ed56d40..1a5cdab78d 100644 --- a/includes/libs/rdbms/database/DatabaseMysqli.php +++ b/includes/libs/rdbms/database/DatabaseMysqli.php @@ -78,9 +78,7 @@ class DatabaseMysqli extends DatabaseMysqlBase { } elseif ( substr_count( $realServer, ':' ) == 1 ) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location - $hostAndSocket = explode( ':', $realServer, 2 ); - $realServer = $hostAndSocket[0]; - $socket = $hostAndSocket[1]; + list( $realServer, $socket ) = explode( ':', $realServer, 2 ); } $mysqli = mysqli_init(); @@ -180,25 +178,6 @@ class DatabaseMysqli extends DatabaseMysqlBase { return $conn->affected_rows; } - function doSelectDomain( DatabaseDomain $domain ) { - if ( $domain->getSchema() !== null ) { - throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." ); - } - - $database = $domain->getDatabase(); - if ( $database !== $this->getDBname() ) { - $conn = $this->getBindingHandle(); - if ( !$conn->select_db( $database ) ) { - throw new DBExpectedError( $this, "Could not select database '$database'." ); - } - } - - // Update that domain fields on success (no exception thrown) - $this->currentDomain = $domain; - - return true; - } - /** * @param mysqli_result $res * @return bool diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php index 481dd9a6be..3041a332c0 100644 --- a/includes/libs/rdbms/database/DatabasePostgres.php +++ b/includes/libs/rdbms/database/DatabasePostgres.php @@ -116,7 +116,7 @@ class DatabasePostgres extends Database { $connectVars['port'] = (int)$this->port; } if ( $this->flags & self::DBO_SSL ) { - $connectVars['sslmode'] = 1; + $connectVars['sslmode'] = 'require'; } $this->connectString = $this->makeConnectionString( $connectVars ); @@ -819,8 +819,12 @@ __INDEXATTR__; $temporary = $temporary ? 'TEMPORARY' : ''; - $ret = $this->query( "CREATE $temporary TABLE $newNameE " . - "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)", $fname ); + $ret = $this->query( + "CREATE $temporary TABLE $newNameE " . + "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)", + $fname, + $this::QUERY_PSEUDO_PERMANENT + ); if ( !$ret ) { return $ret; } @@ -842,7 +846,10 @@ __INDEXATTR__; $fieldE = $this->addIdentifierQuotes( $field ); $newSeqE = $this->addIdentifierQuotes( $newSeq ); $newSeqQ = $this->addQuotes( $newSeq ); - $this->query( "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE", $fname ); + $this->query( + "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE", + $fname + ); $this->query( "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)", $fname @@ -1458,6 +1465,10 @@ SQL; return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false; } + public static function getAttributes() { + return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ]; + } + /** * @param string $lockName * @return string Integer diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php index f2bc01d5b6..82a7e35a31 100644 --- a/includes/libs/rdbms/database/DatabaseSqlite.php +++ b/includes/libs/rdbms/database/DatabaseSqlite.php @@ -1018,7 +1018,7 @@ class DatabaseSqlite extends Database { } } - $res = $this->query( $sql, $fname ); + $res = $this->query( $sql, $fname, self::QUERY_PSEUDO_PERMANENT ); // Take over indexes $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' ); diff --git a/includes/libs/rdbms/database/IDatabase.php b/includes/libs/rdbms/database/IDatabase.php index 6f58cc66a3..eac9baed0e 100644 --- a/includes/libs/rdbms/database/IDatabase.php +++ b/includes/libs/rdbms/database/IDatabase.php @@ -106,6 +106,14 @@ interface IDatabase { /** @var int Enable compression in connection protocol */ const DBO_COMPRESS = 512; + /** @var int Ignore query errors and return false when they happen */ + const QUERY_SILENCE_ERRORS = 1; // b/c for 1.32 query() argument; note that (int)true = 1 + /** + * @var int Treat the TEMPORARY table from the given CREATE query as if it is + * permanent as far as write tracking is concerned. This is useful for testing. + */ + const QUERY_PSEUDO_PERMANENT = 2; + /** * A string describing the current software version, and possibly * other details in a user-friendly way. Will be listed on Special:Version, etc. @@ -527,13 +535,13 @@ interface IDatabase { * @param string $sql SQL query * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) - * @param bool $tempIgnore Whether to avoid throwing an exception on errors... - * maybe best to catch the exception instead? + * @param int $flags Bitfield of IDatabase::QUERY_* constants. Note that suppression + * of errors is best handled by try/catch rather than using one of these flags. * @return bool|IResultWrapper True for a successful write query, IResultWrapper object - * for a successful read query, or false on failure if $tempIgnore set + * for a successful read query, or false on failure if QUERY_SILENCE_ERRORS is set. * @throws DBError */ - public function query( $sql, $fname = __METHOD__, $tempIgnore = false ); + public function query( $sql, $fname = __METHOD__, $flags = 0 ); /** * Free a result object returned by query() or select(). It's usually not diff --git a/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php index 1355e2263e..a9befc2bae 100644 --- a/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php +++ b/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php @@ -69,7 +69,6 @@ class ResultWrapper implements IResultWrapper { public function free() { if ( $this->db ) { - $this->db->freeResult( $this ); $this->db = null; } $this->result = null; diff --git a/includes/libs/rdbms/exception/DBQueryError.php b/includes/libs/rdbms/exception/DBQueryError.php index b1353b793e..9b664fc3d0 100644 --- a/includes/libs/rdbms/exception/DBQueryError.php +++ b/includes/libs/rdbms/exception/DBQueryError.php @@ -45,7 +45,7 @@ class DBQueryError extends DBExpectedError { public function __construct( IDatabase $db, $error, $errno, $sql, $fname, $message = null ) { if ( $message === null ) { if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) { - $message = "A connection error occurred. \n" . + $message = "A connection error occurred during a query. \n" . "Query: $sql\n" . "Function: $fname\n" . "Error: $errno $error\n"; diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php b/includes/libs/rdbms/lbfactory/LBFactory.php index 007ac20662..3a8f2e1fcd 100644 --- a/includes/libs/rdbms/lbfactory/LBFactory.php +++ b/includes/libs/rdbms/lbfactory/LBFactory.php @@ -654,7 +654,7 @@ abstract class LBFactory implements ILBFactory { } public function closeAll() { - $this->forEachLBCallMethod( 'closeAll', [] ); + $this->forEachLBCallMethod( 'closeAll' ); } public function setAgentName( $agent ) { diff --git a/includes/libs/rdbms/lbfactory/LBFactoryMulti.php b/includes/libs/rdbms/lbfactory/LBFactoryMulti.php index 189ceee56d..aec99f4ec7 100644 --- a/includes/libs/rdbms/lbfactory/LBFactoryMulti.php +++ b/includes/libs/rdbms/lbfactory/LBFactoryMulti.php @@ -89,9 +89,6 @@ class LBFactoryMulti extends LBFactory { */ private $readOnlyBySection = []; - /** @var array Load balancer factory configuration */ - private $conf; - /** @var LoadBalancer[] */ private $mainLBs = []; @@ -166,7 +163,6 @@ class LBFactoryMulti extends LBFactory { public function __construct( array $conf ) { parent::__construct( $conf ); - $this->conf = $conf; $required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ]; $optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName', 'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer', diff --git a/includes/linkeddata/PageDataRequestHandler.php b/includes/linkeddata/PageDataRequestHandler.php index 61efba02b8..93aa89f724 100644 --- a/includes/linkeddata/PageDataRequestHandler.php +++ b/includes/linkeddata/PageDataRequestHandler.php @@ -43,11 +43,7 @@ class PageDataRequestHandler { */ public function canHandleRequest( $subPage, WebRequest $request ) { if ( $subPage === '' || $subPage === null ) { - if ( $request->getText( 'target', '' ) === '' ) { - return false; - } else { - return true; - } + return $request->getText( 'target' ) !== ''; } $parts = explode( '/', $subPage, 2 ); @@ -90,7 +86,7 @@ class PageDataRequestHandler { if ( $subPage !== '' ) { $title = $parts[1]; } else { - $title = $request->getText( 'target', '' ); + $title = $request->getText( 'target' ); } $revision = $request->getInt( 'oldid', $revision ); @@ -145,7 +141,7 @@ class PageDataRequestHandler { } $negotiator = new HttpAcceptNegotiator( $mimeTypes ); - $format = $negotiator->getBestSupportedKey( $accept, null ); + $format = $negotiator->getBestSupportedKey( $accept ); if ( $format === null ) { $format = isset( $accept['text/html'] ) ? 'text/html' : null; diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php index 33dd69b8fd..5cad31f2fe 100644 --- a/includes/logging/LogEntry.php +++ b/includes/logging/LogEntry.php @@ -793,8 +793,24 @@ class ManualLogEntry extends LogEntryBase implements Taggable { * @param string $to One of: rcandudp (default), rc, udp */ public function publish( $newId, $to = 'rcandudp' ) { + $canAddTags = true; + // FIXME: this code should be removed once all callers properly call publish() + if ( $to === 'udp' && !$newId && !$this->getAssociatedRevId() ) { + \MediaWiki\Logger\LoggerFactory::getInstance( 'logging' )->warning( + 'newId and/or revId must be set when calling ManualLogEntry::publish()', + [ + 'newId' => $newId, + 'to' => $to, + 'revId' => $this->getAssociatedRevId(), + // pass a new exception to register the stack trace + 'exception' => new RuntimeException() + ] + ); + $canAddTags = false; + } + DeferredUpdates::addCallableUpdate( - function () use ( $newId, $to ) { + function () use ( $newId, $to, $canAddTags ) { $log = new LogPage( $this->getType() ); if ( !$log->isRestricted() ) { Hooks::runWithoutAbort( 'ManualLogEntryBeforePublish', [ $this ] ); @@ -806,9 +822,14 @@ class ManualLogEntry extends LogEntryBase implements Taggable { $rc->save( $rc::SEND_NONE ); } else { $tags = $this->getTags(); - if ( $tags ) { - $revId = $this->getAssociatedRevId(); // Use null if $revId is 0 - ChangeTags::addTags( $tags, null, $revId > 0 ? $revId : null, $newId ); + if ( $tags && $canAddTags ) { + $revId = $this->getAssociatedRevId(); + ChangeTags::addTags( + $tags, + null, + $revId > 0 ? $revId : null, + $newId > 0 ? $newId : null + ); } } diff --git a/includes/media/MediaHandlerFactory.php b/includes/media/MediaHandlerFactory.php index 543dc80dfd..82e8d1ffc2 100644 --- a/includes/media/MediaHandlerFactory.php +++ b/includes/media/MediaHandlerFactory.php @@ -66,11 +66,7 @@ class MediaHandlerFactory { } protected function getHandlerClass( $type ) { - if ( isset( $this->registry[$type] ) ) { - return $this->registry[$type]; - } else { - return false; - } + return $this->registry[$type] ?? false; } /** diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php index dc8b146c3a..fed085425a 100644 --- a/includes/objectcache/ObjectCache.php +++ b/includes/objectcache/ObjectCache.php @@ -238,7 +238,6 @@ class ObjectCache { global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType; $candidates = [ $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType ]; foreach ( $candidates as $candidate ) { - $cache = false; if ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) { $cache = self::getInstance( $candidate ); // CACHE_ACCEL might default to nothing if no APCu diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php index b2d61a8925..088f94e37c 100644 --- a/includes/objectcache/SqlBagOStuff.php +++ b/includes/objectcache/SqlBagOStuff.php @@ -234,22 +234,34 @@ class SqlBagOStuff extends BagOStuff { } } - protected function doGet( $key, $flags = 0 ) { + protected function doGet( $key, $flags = 0, &$casToken = null ) { $casToken = null; - return $this->getWithToken( $key, $casToken, $flags ); - } + $blobs = $this->fetchBlobMulti( [ $key ] ); + if ( array_key_exists( $key, $blobs ) ) { + $blob = $blobs[$key]; + $value = $this->unserialize( $blob ); + + $casToken = ( $value !== false ) ? $blob : null; - protected function getWithToken( $key, &$casToken, $flags = 0 ) { - $values = $this->getMulti( [ $key ] ); - if ( array_key_exists( $key, $values ) ) { - $casToken = $values[$key]; - return $values[$key]; + return $value; } + return false; } public function getMulti( array $keys, $flags = 0 ) { + $values = []; + + $blobs = $this->fetchBlobMulti( $keys ); + foreach ( $blobs as $key => $blob ) { + $values[$key] = $this->unserialize( $blob ); + } + + return $values; + } + + public function fetchBlobMulti( array $keys, $flags = 0 ) { $values = []; // array of (key => value) $keysByTable = []; @@ -298,7 +310,7 @@ class SqlBagOStuff extends BagOStuff { if ( $this->isExpired( $db, $row->exptime ) ) { // MISS $this->debug( "get: key has expired" ); } else { // HIT - $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) ); + $values[$key] = $db->decodeBlob( $row->value ); } } catch ( DBQueryError $e ) { $this->handleWriteError( $e, $db, $row->serverIndex ); @@ -344,7 +356,7 @@ class SqlBagOStuff extends BagOStuff { if ( $exptime == 0 ) { $encExpiry = $this->getMaxDateTime( $db ); } else { - $exptime = $this->convertExpiry( $exptime ); + $exptime = $this->convertToExpiry( $exptime ); $encExpiry = $db->timestamp( $exptime ); } foreach ( $serverKeys as $tableName => $tableKeys ) { @@ -406,7 +418,7 @@ class SqlBagOStuff extends BagOStuff { if ( $exptime == 0 ) { $encExpiry = $this->getMaxDateTime( $db ); } else { - $exptime = $this->convertExpiry( $exptime ); + $exptime = $this->convertToExpiry( $exptime ); $encExpiry = $db->timestamp( $exptime ); } // (T26425) use a replace if the db supports it instead of @@ -420,7 +432,7 @@ class SqlBagOStuff extends BagOStuff { ], [ 'keyname' => $key, - 'value' => $db->encodeBlob( $this->serialize( $casToken ) ) + 'value' => $db->encodeBlob( $casToken ) ], __METHOD__ ); @@ -519,7 +531,7 @@ class SqlBagOStuff extends BagOStuff { } } catch ( DBError $e ) { $this->handleWriteError( $e, $db, $serverIndex ); - return null; + return false; } return $newValue; @@ -534,20 +546,33 @@ class SqlBagOStuff extends BagOStuff { return $ok; } - public function changeTTL( $key, $expiry = 0, $flags = 0 ) { + public function changeTTL( $key, $exptime = 0, $flags = 0 ) { list( $serverIndex, $tableName ) = $this->getTableByKey( $key ); $db = null; $silenceScope = $this->silenceTransactionProfiler(); try { $db = $this->getDB( $serverIndex ); + if ( $exptime == 0 ) { + $timestamp = $this->getMaxDateTime( $db ); + } else { + $timestamp = $db->timestamp( $this->convertToExpiry( $exptime ) ); + } $db->update( $tableName, - [ 'exptime' => $db->timestamp( $this->convertExpiry( $expiry ) ) ], + [ 'exptime' => $timestamp ], [ 'keyname' => $key, 'exptime > ' . $db->addQuotes( $db->timestamp( time() ) ) ], __METHOD__ ); if ( $db->affectedRows() == 0 ) { - return false; + $exists = (bool)$db->selectField( + $tableName, + 1, + [ 'keyname' => $key, 'exptime' => $timestamp ], + __METHOD__, + [ 'FOR UPDATE' ] + ); + + return $exists; } } catch ( DBError $e ) { $this->handleWriteError( $e, $db, $serverIndex ); diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index 66804bc152..60237ff1ab 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -321,9 +321,7 @@ class ImagePage extends Article { $dirmark = $lang->getDirMarkEntity(); $request = $this->getContext()->getRequest(); - $max = $this->getImageLimitsFromOption( $user, 'imagesize' ); - $maxWidth = $max[0]; - $maxHeight = $max[1]; + list( $maxWidth, $maxHeight ) = $this->getImageLimitsFromOption( $user, 'imagesize' ); if ( $this->displayImg->exists() ) { # image @@ -1029,7 +1027,7 @@ EOT * * @param User $user * @param string $optionName Name of a option to check, typically imagesize or thumbsize - * @return array + * @return int[] * @since 1.21 */ public function getImageLimitsFromOption( $user, $optionName ) { diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php index 078c819d6f..64164490c2 100644 --- a/includes/parser/LinkHolderArray.php +++ b/includes/parser/LinkHolderArray.php @@ -632,8 +632,7 @@ class LinkHolderArray { * @private */ public function replaceTextCallback( $matches ) { - $type = $matches[1]; - $key = $matches[2]; + list( , $type, $key ) = $matches; if ( $type == 'LINK' ) { list( $ns, $index ) = explode( ':', $key, 2 ); if ( isset( $this->internals[$ns][$index]['text'] ) ) { diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 546152f6c9..0440e89408 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -1045,10 +1045,7 @@ class Parser { $inside = $p[5]; } else { # tag - $element = $p[1]; - $attributes = $p[2]; - $close = $p[3]; - $inside = $p[4]; + list( , $element, $attributes, $close, $inside ) = $p; } $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX; @@ -1072,8 +1069,7 @@ class Parser { $tail = ''; $text = ''; } else { - $tail = $q[1]; - $text = $q[2]; + list( , $tail, $text ) = $q; } } @@ -2240,8 +2236,7 @@ class Parser { if ( $useLinkPrefixExtension ) { if ( preg_match( $e2, $s, $m ) ) { - $prefix = $m[2]; - $s = $m[1]; + list( , $s, $prefix ) = $m; } else { $prefix = ''; } diff --git a/includes/poolcounter/PoolCounter.php b/includes/poolcounter/PoolCounter.php index ba0b4cb318..060faec52e 100644 --- a/includes/poolcounter/PoolCounter.php +++ b/includes/poolcounter/PoolCounter.php @@ -39,7 +39,7 @@ * that start with "nowait:". However, only 0 timeouts (non-blocking requests) * can be used with "nowait:" keys. * - * By default PoolCounter_Stub is used, which provides no locking. You + * By default PoolCounterNull is used, which provides no locking. You * can get a useful one in the PoolCounter extension. */ abstract class PoolCounter { @@ -111,7 +111,7 @@ abstract class PoolCounter { public static function factory( $type, $key ) { global $wgPoolCounterConf; if ( !isset( $wgPoolCounterConf[$type] ) ) { - return new PoolCounter_Stub; + return new PoolCounterNull; } $conf = $wgPoolCounterConf[$type]; $class = $conf['class']; @@ -208,23 +208,3 @@ abstract class PoolCounter { return $type . ':' . ( hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots ); } } - -// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps -class PoolCounter_Stub extends PoolCounter { - - public function __construct() { - /* No parameters needed */ - } - - public function acquireForMe() { - return Status::newGood( PoolCounter::LOCKED ); - } - - public function acquireForAnyone() { - return Status::newGood( PoolCounter::LOCKED ); - } - - public function release() { - return Status::newGood( PoolCounter::RELEASED ); - } -} diff --git a/includes/poolcounter/PoolCounterNull.php b/includes/poolcounter/PoolCounterNull.php new file mode 100644 index 0000000000..95a5057131 --- /dev/null +++ b/includes/poolcounter/PoolCounterNull.php @@ -0,0 +1,44 @@ +publish( $channel, $line ); return true; - } else { - return false; } + + return false; } } diff --git a/includes/registration/ExtensionJsonValidator.php b/includes/registration/ExtensionJsonValidator.php index 9d6c1a5b33..ba5df52854 100644 --- a/includes/registration/ExtensionJsonValidator.php +++ b/includes/registration/ExtensionJsonValidator.php @@ -60,12 +60,16 @@ class ExtensionJsonValidator { 'The JsonSchema library cannot be found, please install it through composer.' ); return false; - } elseif ( !class_exists( SpdxLicenses::class ) ) { + } + + if ( !class_exists( SpdxLicenses::class ) ) { call_user_func( $this->missingDepCallback, 'The spdx-licenses library cannot be found, please install it through composer.' ); return false; - } elseif ( !class_exists( JsonParser::class ) ) { + } + + if ( !class_exists( JsonParser::class ) ) { call_user_func( $this->missingDepCallback, 'The JSON lint library cannot be found, please install it through composer.' ); @@ -104,7 +108,9 @@ class ExtensionJsonValidator { throw new ExtensionJsonValidationError( "$path is using a non-supported schema version" ); - } elseif ( $version > ExtensionRegistry::MANIFEST_VERSION ) { + } + + if ( $version > ExtensionRegistry::MANIFEST_VERSION ) { throw new ExtensionJsonValidationError( "$path is using a non-supported schema version" ); @@ -140,15 +146,15 @@ class ExtensionJsonValidator { if ( $validator->isValid() && !$extraErrors ) { // All good. return true; - } else { - $out = "$path did not pass validation.\n"; - foreach ( $validator->getErrors() as $error ) { - $out .= "[{$error['property']}] {$error['message']}\n"; - } - if ( $extraErrors ) { - $out .= implode( "\n", $extraErrors ) . "\n"; - } - throw new ExtensionJsonValidationError( $out ); } + + $out = "$path did not pass validation.\n"; + foreach ( $validator->getErrors() as $error ) { + $out .= "[{$error['property']}] {$error['message']}\n"; + } + if ( $extraErrors ) { + $out .= implode( "\n", $extraErrors ) . "\n"; + } + throw new ExtensionJsonValidationError( $out ); } } diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index 1d3fd86d8b..b474ddc766 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -189,7 +189,6 @@ class ExtensionProcessor implements Processor { * @param string $path * @param array $info * @param int $version manifest_version for info - * @return array */ public function extractInfo( $path, array $info, $version ) { $dir = dirname( $path ); diff --git a/includes/registration/Processor.php b/includes/registration/Processor.php index 636d3b3768..68ba4130a0 100644 --- a/includes/registration/Processor.php +++ b/includes/registration/Processor.php @@ -17,7 +17,6 @@ interface Processor { * @param string $path Absolute path of JSON file * @param array $info * @param int $version manifest_version for info - * @return array "credits" information to store */ public function extractInfo( $path, array $info, $version ); diff --git a/includes/revisionlist/RevisionItem.php b/includes/revisionlist/RevisionItem.php new file mode 100644 index 0000000000..faf8d82e79 --- /dev/null +++ b/includes/revisionlist/RevisionItem.php @@ -0,0 +1,135 @@ +revision = new Revision( $row ); + $this->context = $list->getContext(); + } + + public function getIdField() { + return 'rev_id'; + } + + public function getTimestampField() { + return 'rev_timestamp'; + } + + public function getAuthorIdField() { + return 'rev_user'; + } + + public function getAuthorNameField() { + return 'rev_user_text'; + } + + public function canView() { + return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() ); + } + + public function canViewContent() { + return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() ); + } + + public function isDeleted() { + return $this->revision->isDeleted( Revision::DELETED_TEXT ); + } + + /** + * Get the HTML link to the revision text. + * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + protected function getRevisionLink() { + $date = $this->list->getLanguage()->userTimeAndDate( + $this->revision->getTimestamp(), $this->list->getUser() ); + + if ( $this->isDeleted() && !$this->canViewContent() ) { + return htmlspecialchars( $date ); + } + $linkRenderer = $this->getLinkRenderer(); + return $linkRenderer->makeKnownLink( + $this->list->title, + $date, + [], + [ + 'oldid' => $this->revision->getId(), + 'unhide' => 1 + ] + ); + } + + /** + * Get the HTML link to the diff. + * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + protected function getDiffLink() { + if ( $this->isDeleted() && !$this->canViewContent() ) { + return $this->context->msg( 'diff' )->escaped(); + } else { + $linkRenderer = $this->getLinkRenderer(); + return $linkRenderer->makeKnownLink( + $this->list->title, + $this->list->msg( 'diff' )->text(), + [], + [ + 'diff' => $this->revision->getId(), + 'oldid' => 'prev', + 'unhide' => 1 + ] + ); + } + } + + /** + * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + public function getHTML() { + $difflink = $this->context->msg( 'parentheses' ) + ->rawParams( $this->getDiffLink() )->escaped(); + $revlink = $this->getRevisionLink(); + $userlink = Linker::revUserLink( $this->revision ); + $comment = Linker::revComment( $this->revision ); + if ( $this->isDeleted() ) { + $revlink = "$revlink"; + } + return "
  • $difflink $revlink $userlink $comment
  • "; + } +} diff --git a/includes/revisionlist/RevisionItemBase.php b/includes/revisionlist/RevisionItemBase.php new file mode 100644 index 0000000000..dcb2f39932 --- /dev/null +++ b/includes/revisionlist/RevisionItemBase.php @@ -0,0 +1,177 @@ +list = $list; + $this->row = $row; + } + + /** + * Get the DB field name associated with the ID list. + * Override this function. + * @return null + */ + public function getIdField() { + return null; + } + + /** + * Get the DB field name storing timestamps. + * Override this function. + * @return bool + */ + public function getTimestampField() { + return false; + } + + /** + * Get the DB field name storing user ids. + * Override this function. + * @return bool + */ + public function getAuthorIdField() { + return false; + } + + /** + * Get the DB field name storing user names. + * Override this function. + * @return bool + */ + public function getAuthorNameField() { + return false; + } + + /** + * Get the DB field name storing actor ids. + * Override this function. + * @since 1.31 + * @return bool + */ + public function getAuthorActorField() { + return false; + } + + /** + * Get the ID, as it would appear in the ids URL parameter + * @return int + */ + public function getId() { + $field = $this->getIdField(); + return $this->row->$field; + } + + /** + * Get the date, formatted in user's language + * @return string + */ + public function formatDate() { + return $this->list->getLanguage()->userDate( $this->getTimestamp(), + $this->list->getUser() ); + } + + /** + * Get the time, formatted in user's language + * @return string + */ + public function formatTime() { + return $this->list->getLanguage()->userTime( $this->getTimestamp(), + $this->list->getUser() ); + } + + /** + * Get the timestamp in MW 14-char form + * @return mixed + */ + public function getTimestamp() { + $field = $this->getTimestampField(); + return wfTimestamp( TS_MW, $this->row->$field ); + } + + /** + * Get the author user ID + * @return int + */ + public function getAuthorId() { + $field = $this->getAuthorIdField(); + return intval( $this->row->$field ); + } + + /** + * Get the author user name + * @return string + */ + public function getAuthorName() { + $field = $this->getAuthorNameField(); + return strval( $this->row->$field ); + } + + /** + * Get the author actor ID + * @since 1.31 + * @return string + */ + public function getAuthorActor() { + $field = $this->getAuthorActorField(); + return strval( $this->row->$field ); + } + + /** + * Returns true if the current user can view the item + */ + abstract public function canView(); + + /** + * Returns true if the current user can view the item text/file + */ + abstract public function canViewContent(); + + /** + * Get the HTML of the list item. Should be include "
  • " tags. + * This is used to show the list in HTML form, by the special page. + */ + abstract public function getHTML(); + + /** + * Returns an instance of LinkRenderer + * @return \MediaWiki\Linker\LinkRenderer + */ + protected function getLinkRenderer() { + return MediaWikiServices::getInstance()->getLinkRenderer(); + } +} diff --git a/includes/revisionlist/RevisionList.php b/includes/revisionlist/RevisionList.php new file mode 100644 index 0000000000..e7fab9b8c6 --- /dev/null +++ b/includes/revisionlist/RevisionList.php @@ -0,0 +1,53 @@ + $this->title->getArticleID() ]; + if ( $this->ids !== null ) { + $conds['rev_id'] = array_map( 'intval', $this->ids ); + } + $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] ); + return $db->select( + $revQuery['tables'], + $revQuery['fields'], + $conds, + __METHOD__, + [ 'ORDER BY' => 'rev_id DESC' ], + $revQuery['joins'] + ); + } + + public function newItem( $row ) { + return new RevisionItem( $this, $row ); + } +} diff --git a/includes/revisionlist/RevisionListBase.php b/includes/revisionlist/RevisionListBase.php new file mode 100644 index 0000000000..fb379c953a --- /dev/null +++ b/includes/revisionlist/RevisionListBase.php @@ -0,0 +1,148 @@ +setContext( $context ); + $this->title = $title; + } + + /** + * Select items only where the ID is any of the specified values + * @param array $ids + */ + function filterByIds( array $ids ) { + $this->ids = $ids; + } + + /** + * Get the internal type name of this list. Equal to the table name. + * Override this function. + * @return null + */ + public function getType() { + return null; + } + + /** + * Initialise the current iteration pointer + */ + protected function initCurrent() { + $row = $this->res->current(); + if ( $row ) { + $this->current = $this->newItem( $row ); + } else { + $this->current = false; + } + } + + /** + * Start iteration. This must be called before current() or next(). + * @return Revision First list item + */ + public function reset() { + if ( !$this->res ) { + $this->res = $this->doQuery( wfGetDB( DB_REPLICA ) ); + } else { + $this->res->rewind(); + } + $this->initCurrent(); + return $this->current; + } + + public function rewind() { + $this->reset(); + } + + /** + * Get the current list item, or false if we are at the end + * @return Revision + */ + public function current() { + return $this->current; + } + + /** + * Move the iteration pointer to the next list item, and return it. + * @return Revision + */ + public function next() { + $this->res->next(); + $this->initCurrent(); + return $this->current; + } + + public function key() { + return $this->res ? $this->res->key() : 0; + } + + public function valid() { + return $this->res ? $this->res->valid() : false; + } + + /** + * Get the number of items in the list. + * @return int + */ + public function length() { + if ( !$this->res ) { + return 0; + } else { + return $this->res->numRows(); + } + } + + /** + * Do the DB query to iterate through the objects. + * @param IDatabase $db DB object to use for the query + */ + abstract public function doQuery( $db ); + + /** + * Create an item object from a DB result row + * @param object $row + */ + abstract public function newItem( $row ); +} diff --git a/includes/session/SessionBackend.php b/includes/session/SessionBackend.php index 7956e9fecb..0ea13e2a91 100644 --- a/includes/session/SessionBackend.php +++ b/includes/session/SessionBackend.php @@ -270,6 +270,8 @@ final class SessionBackend { // Delete the data for the old session ID now $this->store->delete( $this->store->makeKey( 'MWSession', $oldId ) ); } + + return $this->id; } /** diff --git a/includes/specialpage/QueryPage.php b/includes/specialpage/QueryPage.php index 579ca195d8..b88479ade7 100644 --- a/includes/specialpage/QueryPage.php +++ b/includes/specialpage/QueryPage.php @@ -46,13 +46,18 @@ abstract class QueryPage extends SpecialPage { * The number of rows returned by the query. Reading this variable * only makes sense in functions that are run after the query has been * done, such as preprocessResults() and formatRow(). + * + * @var int */ protected $numRows; + /** + * @var string|null + */ protected $cachedTimestamp = null; /** - * Whether to show prev/next links + * @var bool Whether to show prev/next links */ protected $shownavigation = true; @@ -62,7 +67,8 @@ abstract class QueryPage extends SpecialPage { * * DO NOT CHANGE THIS LIST without testing that * maintenance/updateSpecialPages.php still works. - * @return array + * + * @return string[][] */ public static function getPages() { static $qp = null; @@ -166,7 +172,7 @@ abstract class QueryPage extends SpecialPage { * Subclasses return an array of fields to order by here. Don't append * DESC to the field names, that'll be done automatically if * sortDescending() returns true. - * @return array + * @return string[] * @since 1.18 */ function getOrderFields() { @@ -378,7 +384,7 @@ abstract class QueryPage extends SpecialPage { /** * Get a DB connection to be used for slow recache queries - * @return IDatabase + * @return \Wikimedia\Rdbms\Database */ function getRecacheDB() { return wfGetDB( DB_REPLICA, [ $this->getName(), 'QueryPage::recache', 'vslow' ] ); @@ -500,6 +506,9 @@ abstract class QueryPage extends SpecialPage { return [ 'value' ]; } + /** + * @return string + */ public function getCachedTimestamp() { if ( is_null( $this->cachedTimestamp ) ) { $dbr = wfGetDB( DB_REPLICA ); @@ -754,98 +763,6 @@ abstract class QueryPage extends SpecialPage { function preprocessResults( $db, $res ) { } - /** - * Similar to above, but packaging in a syndicated feed instead of a web page - * @param string $class - * @param int $limit - * @return bool - */ - function doFeed( $class = '', $limit = 50 ) { - if ( !$this->getConfig()->get( 'Feed' ) ) { - $this->getOutput()->addWikiMsg( 'feed-unavailable' ); - return false; - } - - $limit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) ); - - $feedClasses = $this->getConfig()->get( 'FeedClasses' ); - if ( isset( $feedClasses[$class] ) ) { - /** @var RSSFeed|AtomFeed $feed */ - $feed = new $feedClasses[$class]( - $this->feedTitle(), - $this->feedDesc(), - $this->feedUrl() ); - $feed->outHeader(); - - $res = $this->reallyDoQuery( $limit, 0 ); - foreach ( $res as $obj ) { - $item = $this->feedResult( $obj ); - if ( $item ) { - $feed->outItem( $item ); - } - } - - $feed->outFooter(); - return true; - } else { - return false; - } - } - - /** - * Override for custom handling. If the titles/links are ok, just do - * feedItemDesc() - * @param object $row - * @return FeedItem|null - */ - function feedResult( $row ) { - if ( !isset( $row->title ) ) { - return null; - } - $title = Title::makeTitle( intval( $row->namespace ), $row->title ); - if ( $title ) { - $date = $row->timestamp ?? ''; - $comments = ''; - if ( $title ) { - $talkpage = $title->getTalkPage(); - $comments = $talkpage->getFullURL(); - } - - return new FeedItem( - $title->getPrefixedText(), - $this->feedItemDesc( $row ), - $title->getFullURL(), - $date, - $this->feedItemAuthor( $row ), - $comments ); - } else { - return null; - } - } - - function feedItemDesc( $row ) { - return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; - } - - function feedItemAuthor( $row ) { - return $row->user_text ?? ''; - } - - function feedTitle() { - $desc = $this->getDescription(); - $code = $this->getConfig()->get( 'LanguageCode' ); - $sitename = $this->getConfig()->get( 'Sitename' ); - return "$sitename - $desc [$code]"; - } - - function feedDesc() { - return $this->msg( 'tagline' )->text(); - } - - function feedUrl() { - return $this->getPageTitle()->getFullURL(); - } - /** * Creates a new LinkBatch object, adds all pages from the passed ResultWrapper (MUST include * title and optional the namespace field) and executes the batch. This operation will pre-cache diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php index 02a8d009d4..0cf790bdda 100644 --- a/includes/specials/SpecialBlock.php +++ b/includes/specials/SpecialBlock.php @@ -350,9 +350,11 @@ class SpecialBlock extends FormSpecialPage { $block = Block::newFromTarget( $this->target ); - if ( $block instanceof Block && !$block->mAuto # The block exists and isn't an autoblock - && ( $this->type != Block::TYPE_RANGE # The block isn't a rangeblock - || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block + // Populate fields if there is a block that is not an autoblock; if it is a range + // block, only populate the fields if the range is the same as $this->target + if ( $block instanceof Block && $block->getType() !== Block::TYPE_AUTO + && ( $this->type != Block::TYPE_RANGE + || $block->getTarget() == $this->target ) ) { $fields['HardBlock']['default'] = $block->isHardblock(); $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked(); @@ -363,7 +365,7 @@ class SpecialBlock extends FormSpecialPage { } if ( isset( $fields['HideUser'] ) ) { - $fields['HideUser']['default'] = $block->mHideName; + $fields['HideUser']['default'] = $block->getHideName(); } if ( isset( $fields['DisableUTEdit'] ) ) { @@ -372,8 +374,8 @@ class SpecialBlock extends FormSpecialPage { // If the username was hidden (ipb_deleted == 1), don't show the reason // unless this user also has rights to hideuser: T37839 - if ( !$block->mHideName || $this->getUser()->isAllowed( 'hideuser' ) ) { - $fields['Reason']['default'] = $block->mReason; + if ( !$block->getHideName() || $this->getUser()->isAllowed( 'hideuser' ) ) { + $fields['Reason']['default'] = $block->getReason(); } else { $fields['Reason']['default'] = ''; } @@ -389,10 +391,10 @@ class SpecialBlock extends FormSpecialPage { $fields['Confirm']['default'] = 1; } - if ( $block->mExpiry == 'infinity' ) { + if ( $block->getExpiry() == 'infinity' ) { $fields['Expiry']['default'] = 'infinite'; } else { - $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->mExpiry ); + $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() ); } $fields['BlockId']['default'] = $block->getId(); @@ -873,14 +875,14 @@ class SpecialBlock extends FormSpecialPage { $block = new Block(); $block->setTarget( $target ); $block->setBlocker( $performer ); - $block->mReason = $data['Reason'][0]; - $block->mExpiry = $expiryTime; + $block->setReason( $data['Reason'][0] ); + $block->setExpiry( $expiryTime ); $block->isCreateAccountBlocked( $data['CreateAccount'] ); $block->isUsertalkEditAllowed( !$wgBlockAllowsUTEdit || !$data['DisableUTEdit'] ); $block->isEmailBlocked( $data['DisableEmail'] ); $block->isHardblock( $data['HardBlock'] ); $block->isAutoblocking( $data['AutoBlock'] ); - $block->mHideName = $data['HideUser']; + $block->setHideName( $data['HideUser'] ); if ( $isPartialBlock ) { $block->isSitewide( false ); @@ -939,19 +941,19 @@ class SpecialBlock extends FormSpecialPage { } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... - if ( $currentBlock->mHideName && !$performer->isAllowed( 'hideuser' ) ) { + if ( $currentBlock->getHideName() && !$performer->isAllowed( 'hideuser' ) ) { return [ 'cant-see-hidden-user' ]; } $priorBlock = clone $currentBlock; $currentBlock->isHardblock( $block->isHardblock() ); $currentBlock->isCreateAccountBlocked( $block->isCreateAccountBlocked() ); - $currentBlock->mExpiry = $block->mExpiry; + $currentBlock->setExpiry( $block->getExpiry() ); $currentBlock->isAutoblocking( $block->isAutoblocking() ); - $currentBlock->mHideName = $block->mHideName; + $currentBlock->setHideName( $block->getHideName() ); $currentBlock->isEmailBlocked( $block->isEmailBlocked() ); $currentBlock->isUsertalkEditAllowed( $block->isUsertalkEditAllowed() ); - $currentBlock->mReason = $block->mReason; + $currentBlock->setReason( $block->getReason() ); if ( $enablePartialBlocks ) { // Maintain the sitewide status. If partial blocks is not enabled, @@ -970,12 +972,12 @@ class SpecialBlock extends FormSpecialPage { $logaction = 'reblock'; # Unset _deleted fields if requested - if ( $currentBlock->mHideName && !$data['HideUser'] ) { + if ( $currentBlock->getHideName() && !$data['HideUser'] ) { RevisionDeleteUser::unsuppressUserName( $target, $userId ); } # If hiding/unhiding a name, this should go in the private logs - if ( (bool)$currentBlock->mHideName ) { + if ( (bool)$currentBlock->getHideName() ) { $data['HideUser'] = true; } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index c0303b255c..f60d5f02db 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -244,9 +244,9 @@ class SpecialContributions extends IncludableSpecialPage { $output = $pager->getBody(); if ( !$this->including() ) { - $output = '

    ' . $pager->getNavigationBar() . '

    ' . + $output = $pager->getNavigationBar() . $output . - '

    ' . $pager->getNavigationBar() . '

    '; + $pager->getNavigationBar(); } $out->addHTML( $output ); } @@ -353,7 +353,9 @@ class SpecialContributions extends IncludableSpecialPage { } } - return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() ); + return Html::rawElement( 'div', [ 'class' => 'mw-contributions-user-tools' ], + $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() ) + ); } /** diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php index 632415cc1d..a04fe4e16d 100644 --- a/includes/specials/SpecialUnblock.php +++ b/includes/specials/SpecialUnblock.php @@ -205,7 +205,7 @@ class SpecialUnblock extends SpecialPage { # If the name was hidden and the blocking user cannot hide # names, then don't allow any block removals... - if ( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) { + if ( !$performer->isAllowed( 'hideuser' ) && $block->getHideName() ) { return [ 'unblock-hideuser' ]; } @@ -222,7 +222,7 @@ class SpecialUnblock extends SpecialPage { Hooks::run( 'UnblockUserComplete', [ $block, $performer ] ); # Unset _deleted fields as needed - if ( $block->mHideName ) { + if ( $block->getHideName() ) { # Something is deeply FUBAR if this is not a User object, but who knows? $id = $block->getTarget() instanceof User ? $block->getTarget()->getId() diff --git a/includes/specials/forms/PreferencesFormOOUI.php b/includes/specials/forms/PreferencesFormOOUI.php index 81abf1c8e1..fd98dcbc33 100644 --- a/includes/specials/forms/PreferencesFormOOUI.php +++ b/includes/specials/forms/PreferencesFormOOUI.php @@ -117,45 +117,12 @@ class PreferencesFormOOUI extends OOUIHTMLForm { } protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) { - // to get a user visible effect, wrap the fieldset into a framed panel layout - if ( $isRoot ) { - // Mimic TabPanelLayout - $wrapper = new OOUI\PanelLayout( [ - 'expanded' => false, - 'scrollable' => true, - // Framed and padded for no-JS, frame hidden with CSS - 'framed' => true, - 'infusable' => false, - 'classes' => [ 'oo-ui-stackLayout oo-ui-indexLayout-stackLayout' ] - ] ); - $layout = new OOUI\PanelLayout( [ - 'expanded' => false, - 'scrollable' => true, - 'infusable' => false, - 'classes' => [ 'oo-ui-tabPanelLayout' ] - ] ); - $wrapper->appendContent( $layout ); - } else { - $wrapper = $layout = new OOUI\PanelLayout( [ - 'expanded' => false, - 'padded' => true, - 'framed' => true, - 'infusable' => false, - ] ); - } + $layout = parent::wrapFieldSetSection( $legend, $section, $attributes, $isRoot ); - $layout->appendContent( - new OOUI\FieldsetLayout( [ - 'label' => $legend, - 'infusable' => false, - 'items' => [ - new OOUI\Widget( [ - 'content' => new OOUI\HtmlSnippet( $section ) - ] ), - ], - ] + $attributes ) - ); - return $wrapper; + $layout->addClasses( [ 'mw-prefs-fieldset-wrapper' ] ); + $layout->removeClasses( [ 'oo-ui-panelLayout-framed' ] ); + + return $layout; } /** @@ -163,61 +130,50 @@ class PreferencesFormOOUI extends OOUIHTMLForm { * @return string */ function getBody() { - // Construct fake tabs to avoid FOUC. The structure mimics OOUI's tabPanelLayout. - // TODO: Consider creating an infusable TabPanelLayout in OOUI-PHP. - $fakeTabs = []; - foreach ( $this->getPreferenceSections() as $i => $key ) { - $fakeTabs[] = - Html::rawElement( - 'div', - [ - 'class' => - 'oo-ui-widget oo-ui-widget-enabled oo-ui-optionWidget ' . - 'oo-ui-tabOptionWidget oo-ui-labelElement' . - ( $i === 0 ? ' oo-ui-optionWidget-selected' : '' ) + $tabPanels = []; + foreach ( $this->mFieldTree as $key => $val ) { + if ( !is_array( $val ) ) { + wfDebug( __METHOD__ . " encountered a field not attached to a section: '$key'" ); + continue; + } + $label = $this->getLegend( $key ); + $content = + $this->getHeaderText( $key ) . + $this->displaySection( $this->mFieldTree[$key] ) . + $this->getFooterText( $key ); + + $tabPanels[] = new OOUI\TabPanelLayout( [ + 'classes' => [ 'mw-htmlform-autoinfuse-lazy' ], + 'name' => 'mw-prefsection-' . $key, + 'label' => $label, + 'content' => new OOUI\FieldsetLayout( [ + 'classes' => [ 'mw-prefs-section-fieldset' ], + 'label' => $label, + 'items' => [ + new OOUI\Widget( [ + 'content' => new OOUI\HtmlSnippet( $content ) + ] ), ], - Html::element( - 'a', - [ - 'class' => 'oo-ui-labelElement-label', - // Make this a usable link instead of a span so the tabs - // can be used before JS runs - 'href' => '#mw-prefsection-' . $key - ], - $this->getLegend( $key ) - ) - ); + ] ), + 'expanded' => false, + 'framed' => true, + ] ); } - $fakeTabsHtml = Html::rawElement( - 'div', - [ 'class' => 'oo-ui-layout oo-ui-panelLayout oo-ui-indexLayout-tabPanel' ], - Html::rawElement( - 'div', - [ 'class' => 'oo-ui-widget oo-ui-widget-enabled oo-ui-selectWidget ' . - 'oo-ui-selectWidget-depressed oo-ui-tabSelectWidget' ], - implode( $fakeTabs ) - ) - ); - - return Html::rawElement( - 'div', - [ 'class' => 'oo-ui-layout oo-ui-panelLayout oo-ui-panelLayout-framed mw-prefs-faketabs' ], - Html::rawElement( - 'div', - [ 'class' => 'oo-ui-layout oo-ui-menuLayout oo-ui-menuLayout-static ' . - 'oo-ui-menuLayout-top oo-ui-menuLayout-showMenu oo-ui-indexLayout' ], - Html::rawElement( - 'div', - [ 'class' => 'oo-ui-menuLayout-menu' ], - $fakeTabsHtml - ) . - Html::rawElement( - 'div', - [ 'class' => 'oo-ui-menuLayout-content mw-htmlform-autoinfuse-lazy' ], - $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' ) - ) - ) - ); + + $indexLayout = new OOUI\IndexLayout( [ + 'infusable' => true, + 'expanded' => false, + 'autoFocus' => false, + 'classes' => [ 'mw-prefs-tabs' ], + ] ); + $indexLayout->addTabPanels( $tabPanels ); + + return new OOUI\PanelLayout( [ + 'framed' => true, + 'expanded' => false, + 'classes' => [ 'mw-prefs-tabs-wrapper' ], + 'content' => $indexLayout + ] ); } /** diff --git a/includes/specials/pagers/ContribsPager.php b/includes/specials/pagers/ContribsPager.php index 382ba2fd2b..626fc48c00 100644 --- a/includes/specials/pagers/ContribsPager.php +++ b/includes/specials/pagers/ContribsPager.php @@ -154,6 +154,19 @@ class ContribsPager extends RangeChronologicalPager { return $query; } + /** + * Wrap the navigation bar in a p element with identifying class. + * In future we may want to change the `p` tag to a `div` and upstream + * this to the parent class. + * + * @return string HTML + */ + function getNavigationBar() { + return Html::rawElement( 'p', [ 'class' => 'mw-pager-navigation-bar' ], + parent::getNavigationBar() + ); + } + /** * This method basically executes the exact same code as the parent class, though with * a hook added, to allow extensions to add additional queries. diff --git a/includes/specials/pagers/ProtectedPagesPager.php b/includes/specials/pagers/ProtectedPagesPager.php index bc4202e2af..5583842497 100644 --- a/includes/specials/pagers/ProtectedPagesPager.php +++ b/includes/specials/pagers/ProtectedPagesPager.php @@ -49,7 +49,7 @@ class ProtectedPagesPager extends TablePager { LinkRenderer $linkRenderer ) { $this->mConds = $conds; - $this->type = ( $type ) ? $type : 'edit'; + $this->type = $type ?: 'edit'; $this->level = $level; $this->namespace = $namespace; $this->sizetype = $sizetype; diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index c42584cb1e..d00ad978e7 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -947,8 +947,8 @@ abstract class UploadBase { */ list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); - if ( count( $ext ) ) { - $this->mFinalExtension = trim( $ext[count( $ext ) - 1] ); + if ( $ext !== [] ) { + $this->mFinalExtension = trim( end( $ext ) ); } else { $this->mFinalExtension = ''; diff --git a/includes/user/PasswordReset.php b/includes/user/PasswordReset.php index ef104cce5c..aada3192e3 100644 --- a/includes/user/PasswordReset.php +++ b/includes/user/PasswordReset.php @@ -81,9 +81,7 @@ class PasswordReset implements LoggerAwareInterface { $resetRoutes = $this->config->get( 'PasswordResetRoutes' ); $status = StatusValue::newGood(); - if ( !is_array( $resetRoutes ) || - !in_array( true, array_values( $resetRoutes ), true ) - ) { + if ( !is_array( $resetRoutes ) || !in_array( true, $resetRoutes, true ) ) { // Maybe password resets are disabled, or there are no allowable routes $status = StatusValue::newFatal( 'passwordreset-disabled' ); } elseif ( @@ -265,23 +263,7 @@ class PasswordReset implements LoggerAwareInterface { if ( !$block ) { return false; } - $type = $block->getSystemBlockType(); - if ( in_array( $type, [ null, 'global-block' ], true ) ) { - // Normal block. Maybe it was meant for someone else and the user just needs to log in; - // or maybe it was issued specifically to prevent some IP from messing with password - // reset? Go out on a limb and use the registration allowed flag to decide. - return $block->isCreateAccountBlocked(); - } elseif ( $type === 'proxy' ) { - // we disallow actions through proxy even if the user is logged in - // so it makes sense to disallow password resets as well - return true; - } elseif ( in_array( $type, [ 'dnsbl', 'wgSoftBlockRanges' ], true ) ) { - // these are just meant to force login so let's not prevent that - return false; - } else { - // some extension - we'll have to guess - return true; - } + return $block->appliesToPasswordReset(); } /** diff --git a/includes/user/User.php b/includes/user/User.php index fe15034c4e..3fcba4698d 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -1403,23 +1403,10 @@ class User implements IDBAccessObject, UserIdentity { */ public function trackBlockWithCookie() { $block = $this->getBlock(); - if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) { - $config = RequestContext::getMain()->getConfig(); - $shouldSetCookie = false; - if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) { - // If user is logged-out, set a cookie to track the Block - $shouldSetCookie = in_array( $block->getType(), [ - Block::TYPE_IP, Block::TYPE_RANGE - ] ); - if ( $shouldSetCookie ) { - $block->setCookie( $this->getRequest()->response() ); - } - } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) { - $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking(); - if ( $shouldSetCookie ) { - $block->setCookie( $this->getRequest()->response() ); - } + if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) { + if ( $block->shouldTrackWithCookie( $this->isAnon() ) ) { + $block->setCookie( $this->getRequest()->response() ); } } } @@ -1820,11 +1807,11 @@ class User implements IDBAccessObject, UserIdentity { /** * Get blocking information - * @param bool $bFromReplica Whether to check the replica DB first. + * @param bool $fromReplica Whether to check the replica DB first. * To improve performance, non-critical checks are done against replica DBs. * Check when actually saving should be done against master. */ - private function getBlockedStatus( $bFromReplica = true ) { + private function getBlockedStatus( $fromReplica = true ) { global $wgProxyWhitelist, $wgApplyIpBlocksToXff, $wgSoftBlockRanges; if ( $this->mBlockedby != -1 ) { @@ -1855,7 +1842,7 @@ class User implements IDBAccessObject, UserIdentity { } // User/IP blocking - $block = Block::newFromTarget( $this, $ip, !$bFromReplica ); + $block = Block::newFromTarget( $this, $ip, !$fromReplica ); // Cookie blocking if ( !$block instanceof Block ) { @@ -1891,12 +1878,12 @@ class User implements IDBAccessObject, UserIdentity { $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' ); $xff = array_map( 'trim', explode( ',', $xff ) ); $xff = array_diff( $xff, [ $ip ] ); - $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromReplica ); + $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$fromReplica ); $block = Block::chooseBlock( $xffblocks, $xff ); if ( $block instanceof Block ) { # Mangle the reason to alert the user that the block # originated from matching the X-Forwarded-For header. - $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain(); + $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() ); } } @@ -1918,8 +1905,8 @@ class User implements IDBAccessObject, UserIdentity { wfDebug( __METHOD__ . ": Found block.\n" ); $this->mBlock = $block; $this->mBlockedby = $block->getByName(); - $this->mBlockreason = $block->mReason; - $this->mHideName = $block->mHideName; + $this->mBlockreason = $block->getReason(); + $this->mHideName = $block->getHideName(); $this->mAllowUsertalk = $block->isUsertalkEditAllowed(); } else { $this->mBlock = null; @@ -2263,23 +2250,23 @@ class User implements IDBAccessObject, UserIdentity { /** * Check if user is blocked * - * @param bool $bFromReplica Whether to check the replica DB instead of + * @param bool $fromReplica Whether to check the replica DB instead of * the master. Hacked from false due to horrible probs on site. * @return bool True if blocked, false otherwise */ - public function isBlocked( $bFromReplica = true ) { - return $this->getBlock( $bFromReplica ) instanceof Block && + public function isBlocked( $fromReplica = true ) { + return $this->getBlock( $fromReplica ) instanceof Block && $this->getBlock()->appliesToRight( 'edit' ); } /** * Get the block affecting the user, or null if the user is not blocked * - * @param bool $bFromReplica Whether to check the replica DB instead of the master + * @param bool $fromReplica Whether to check the replica DB instead of the master * @return Block|null */ - public function getBlock( $bFromReplica = true ) { - $this->getBlockedStatus( $bFromReplica ); + public function getBlock( $fromReplica = true ) { + $this->getBlockedStatus( $fromReplica ); return $this->mBlock instanceof Block ? $this->mBlock : null; } diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php index 15c0cf9602..65b50e2d21 100644 --- a/includes/utils/UIDGenerator.php +++ b/includes/utils/UIDGenerator.php @@ -137,8 +137,7 @@ class UIDGenerator { $time = $info['time']; $counter = $info['offsetCounter']; } else { - $time = $info[0]; - $counter = $info[1]; + list( $time, $counter ) = $info; } // Take the 46 LSBs of "milliseconds since epoch" $id_bin = $this->millisecondsSinceEpochBinary( $time ); @@ -192,9 +191,7 @@ class UIDGenerator { $counter = $info['offsetCounter']; $clkSeq = $info['clkSeq']; } else { - $time = $info[0]; - $counter = $info[1]; - $clkSeq = $info[2]; + list( $time, $counter, $clkSeq ) = $info; } // Take the 46 LSBs of "milliseconds since epoch" $id_bin = $this->millisecondsSinceEpochBinary( $time ); diff --git a/languages/Language.php b/languages/Language.php index 9eea7ab1b8..71d350fd28 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -3515,8 +3515,8 @@ class Language { * Truncate a string to a specified number of characters, appending an optional * string (e.g. for ellipsis). * - * This provides multibyte version of truncate() method of this class, suitable for truncation - * based on number of characters, instead of number of bytes. + * This provides multibyte version of truncateForDatabase() method of this class, + * suitable for truncation based on number of characters, instead of number of bytes. * * If $length is negative, the string will be truncated from the beginning. * diff --git a/languages/classes/LanguageKk_cyrl.php b/languages/classes/LanguageKk_cyrl.php index d695be1049..a89dbc2cac 100644 --- a/languages/classes/LanguageKk_cyrl.php +++ b/languages/classes/LanguageKk_cyrl.php @@ -63,9 +63,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "з" ]; // 1st plural, 2nd formal $thirdPerson = [ "ы", "і" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding =& $lastLetter[0]; - $wordLastVowel =& $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -297,9 +295,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "z" ]; // 1st plural, 2nd formal $thirdPerson = [ "ı", "i" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding =& $lastLetter[0]; - $wordLastVowel =& $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -531,9 +527,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "ز" ]; // 1st plural, 2nd formal $thirdPerson = [ "ى", "Ù¸" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding = $lastLetter[0]; - $wordLastVowel = $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -737,7 +731,7 @@ class LanguageKk_cyrl extends Language { /** * @param string $word - * @param array $allVowels + * @param string[] $allVowels * @return array */ function lastLetter( $word, $allVowels ) { diff --git a/languages/i18n/ar.json b/languages/i18n/ar.json index e470bb809c..2355ccbe9d 100644 --- a/languages/i18n/ar.json +++ b/languages/i18n/ar.json @@ -124,6 +124,7 @@ "tog-useeditwarning": "حذّرني عندما أغادر تحرير صفحة فيها تغييرات لم أحفظها", "tog-prefershttps": "استخدم دائما اتصالا آمنا عند تسجيل الدخول", "tog-showrollbackconfirmation": "إظهار رسالة تأكيد عند النقر على رابط الاسترجاع", + "tog-showrollbackconfirmation-prerelease-warning": "تُرجَى الملاحظة: هذه الميزة غير متوفرة بعد؛ إذا قمت بتعيين هذا التفضيل الآن، فسيتم تذكر اختيارك [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status عند إصدار الميزة].", "underline-always": "دائما", "underline-never": "أبدا", "underline-default": "وفق المظهر أو المتصفح", @@ -2320,6 +2321,9 @@ "deleting-backlinks-warning": "تحذير: [[Special:WhatLinksHere/{{FULLPAGENAME}}|صفحات أخرى]] تصل إلى أو تضمن الصفحة التي أنت على وشك حذفها.", "deleting-subpages-warning": "تحذير: الصفحة التي على وشك أن تحذفها لديها [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|صفحة فرعية|$1 صفحات فرعية|51=أكثر من 50 صفحة فرعية}}]].", "rollback": "التراجع عن التعديلات", + "rollback-confirmation-confirm": "يُرجَى التأكيد:", + "rollback-confirmation-yes": "استرجاع", + "rollback-confirmation-no": "إلغاء", "rollbacklink": "استرجع", "rollbacklinkcount": "استرجع {{PLURAL:$1|لا تعديلات|تعديلا واحدا|تعديلين|$1 تعديلات|$1 تعديلاً|تعديل}}", "rollbacklinkcount-morethan": "استرجاع أكثر من {{PLURAL:$1|تعديل|تعديل|تعديلين|$1 تعديلات|$1 تعديلاً|$1 تعديل}}", @@ -3111,6 +3115,7 @@ "confirm-unwatch-top": "إزالة هذه الصفحة من قائمة مراقبتك؟", "confirm-rollback-button": "موافق", "confirm-rollback-top": "استرجاع التعديلات لهذه الصفحة؟", + "confirm-rollback-bottom": "هذا الإجراء سيسترجع فورا التغييرات المحددة في هذه الصفحة.", "confirm-mcrrestore-title": "استرجاع مراجعة", "confirm-mcrundo-title": "الرجوع عن تغيير", "mcrundofailed": "الرجوع فشل", diff --git a/languages/i18n/az.json b/languages/i18n/az.json index b917b9de32..cb005a9ae1 100644 --- a/languages/i18n/az.json +++ b/languages/i18n/az.json @@ -33,7 +33,8 @@ "Neriman2003", "Fitoschido", "Toghrul Rahimli", - "Vlad5250" + "Vlad5250", + "Hüseynzadə" ] }, "tog-underline": "Keçidlərin altını xətlə:", @@ -425,7 +426,7 @@ "userlogin-signwithsecure": "Etibarlı bağlantıdan istifadə edin", "cannotlogin-title": "Daxil olmaq mümkün olmadı", "cannotlogin-text": "Daxil olmaq mümkün deyil.", - "cannotloginnow-title": "Daxil olmaq indi mümkün deyil", + "cannotloginnow-title": "Ä°ndi daxil olmaq mümkün deyil", "cannotloginnow-text": "$1 istifadə edərkən daxil olmaq mümkün deyil.", "cannotcreateaccount-title": "Hesablar yaradıla bilmədi", "cannotcreateaccount-text": "Bu vikidə birbaşa hesab yaratma aktiv deyil.", diff --git a/languages/i18n/be-tarask.json b/languages/i18n/be-tarask.json index 39b9597056..cfe315cef6 100644 --- a/languages/i18n/be-tarask.json +++ b/languages/i18n/be-tarask.json @@ -66,6 +66,7 @@ "tog-useeditwarning": "Папярэджваць мяне, калі я буду пакідаць старонку рэдагаваньня зь незахаванымі зьменамі", "tog-prefershttps": "Заўсёды карыстацца бясьпечным злучэньнем па ўваходзе ў сыстэму", "tog-showrollbackconfirmation": "Паказваць акно пацьвярджэньня пры націсканьні спасылкі адкату", + "tog-showrollbackconfirmation-prerelease-warning": "Калі ласка, заўважце: гэтая магчымасьць яшчэ недаступная. Калі вы вызначаце гэты парамэтар, ваш выбар будзе захаваны да [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status запуску функцыі].", "underline-always": "Заўсёды", "underline-never": "Ніколі", "underline-default": "Паводле браўзэра або афармленьня", @@ -1987,12 +1988,12 @@ "pager-newer-n": "$1 {{PLURAL:$1|навейшая|навейшыя|навейшых}}", "pager-older-n": "$1 {{PLURAL:$1|старэйшая|старэйшыя|старэйшых}}", "suppress": "Падавіць вэрсію", - "querypage-disabled": "Гэта спэцыяльная старонка адключаная для падвышэньня прадукцыйнасьці", + "querypage-disabled": "Гэтая спэцыяльная старонка адключаная для падвышэньня прадукцыйнасьці.", "apihelp": "Даведка API", "apihelp-no-such-module": "Модуль «$1» ня знойдзены.", "apisandbox": "Пясочніца API", "apisandbox-jsonly": "Для выкарыстаньня API-пясочніцы патрэбны JavaScript.", - "apisandbox-api-disabled": "API забаронены на гэтым сайце.", + "apisandbox-api-disabled": "API адключаны на гэтым сайце.", "apisandbox-intro": "Выкарыстоўвайце гэтую старонку для экспэрымэнтаў з API вэб-сэрвісу MediaWiki.\nЗьвяртайцеся да [[mw:API:Main page|дакумэнтацыі API]] для дадатковай інфармацыі па выкарыстаньні API. Напрыклад, [https://www.mediawiki.org/wiki/API#A_simple_example як атрымаць зьмест галоўнай старонкі]. Абярыце дзеяньне, каб пабачыць болей узораў.\n\nЗьвярніце ўвагу, што нягледзячы на тое, што гэта пясочніца, вашыя дзеяньні могуць унесьці зьмены ў вікі.", "apisandbox-submit": "Зрабіць запыт", "apisandbox-reset": "Ачысьціць", @@ -2258,6 +2259,9 @@ "deleting-backlinks-warning": "Увага: [[Special:WhatLinksHere/{{FULLPAGENAME}}|іншыя старонкі]] ўключаюць або спасылаюцца на старонку, якую вы зьбіраецеся выдаліць.", "deleting-subpages-warning": "Папярэджаньне: старонка, якую вы зьбіраецеся выдаліць, мае [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1 падстаронку|$1 падстаронкі|$1 падстаронак|51=болей за 50 падстаронак}}]].", "rollback": "Адкаціць рэдагаваньні", + "rollback-confirmation-confirm": "Калі ласка, пацьвердзіце:", + "rollback-confirmation-yes": "Адкаціць", + "rollback-confirmation-no": "Адмяніць", "rollbacklink": "адкат", "rollbacklinkcount": "адкаціць $1 {{PLURAL:$1|рэдагаваньне|рэдагаваньні|рэдагаваньняў}}", "rollbacklinkcount-morethan": "адкаціць больш за $1 {{PLURAL:$1|рэдагаваньне|рэдагаваньні|рэдагаваньняў}}", @@ -3035,6 +3039,7 @@ "confirm-unwatch-top": "Выдаліць гэтую старонку з Вашага сьпісу назіраньня?", "confirm-rollback-button": "Так", "confirm-rollback-top": "Адкаціць праўкі на гэтай старонцы?", + "confirm-rollback-bottom": "Гэтае дзеяньне імгненна адкоціць абраныя зьмены гэтай старонкі.", "confirm-mcrrestore-title": "Аднавіць вэрсію", "confirm-mcrundo-title": "Адмяніць зьмену", "mcrundofailed": "Адмена не атрымалася", diff --git a/languages/i18n/bg.json b/languages/i18n/bg.json index 12ce570d11..5df588db49 100644 --- a/languages/i18n/bg.json +++ b/languages/i18n/bg.json @@ -3375,6 +3375,9 @@ "pagelang-reason": "Причина", "pagelang-submit": "Изпращане", "pagelang-nonexistent-page": "Страницата $1 не съществува.", + "pagelang-unchanged-language": "Страницата $1 вече е със зададен език $2.", + "pagelang-unchanged-language-default": "Страницата $1 вече е със зададен език, съвпадащ с езика по подразбиране за това уики.", + "pagelang-db-failed": "Базата данни не успя да смени езика на страницата.", "right-pagelang": "Промяна езика на страница", "action-pagelang": "промяна езика на страницата", "log-name-pagelang": "Дневник на езиковите промени", @@ -3399,6 +3402,8 @@ "mediastatistics-header-multimedia": "Мултимедия", "mediastatistics-header-office": "Офис", "mediastatistics-header-total": "Всички файлове", + "json-error-state-mismatch": "Невалиден или грешно структуриран JSON", + "json-error-ctrl-char": "Грешка в контролния знак. Вероятно е неправилно кодиран", "json-error-syntax": "Синтактична грешка", "headline-anchor-title": "Препратка към този раздел", "special-characters-group-latin": "Латиница", @@ -3482,6 +3487,11 @@ "log-action-filter-protect-move_prot": "Преместване на защитата", "log-action-filter-rights-rights": "Ръчна промяна", "log-action-filter-rights-autopromote": "Автоматична промяна", + "log-action-filter-suppress-event": "Потискане на дневника", + "log-action-filter-suppress-revision": "Потискане на версията", + "log-action-filter-suppress-delete": "Потискане на страницата", + "log-action-filter-suppress-block": "Потискане на потребителя чрез блокиране", + "log-action-filter-suppress-reblock": "Потискане на потребителя чрез повторно блокиране", "log-action-filter-upload-upload": "Ново качване", "log-action-filter-upload-overwrite": "Повторно качване", "log-action-filter-upload-revert": "Връщане", diff --git a/languages/i18n/bn.json b/languages/i18n/bn.json index f2c60166a1..7ba93cc5f0 100644 --- a/languages/i18n/bn.json +++ b/languages/i18n/bn.json @@ -2269,6 +2269,9 @@ "deleting-backlinks-warning": "সতর্কীকরণ: আপনি যেটি মুছে ফেলতে যাচ্ছেন তা [[Special:WhatLinksHere/{{FULLPAGENAME}}|অন্যান্য পাতাসমূহে]] সংযুক্ত অথবা অন্তর্ভুক্ত রয়েছে।", "deleting-subpages-warning": "সতর্কীকরণ: আপনি যে পাতাটি মুছে ফেলতে যাচ্ছেন তাঁর [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|একটি উপপাতা|$1টি উপপাতা|51=৫০টির বেশী}}]] রয়েছে।", "rollback": "সম্পাদনা ফিরিয়ে নিন", + "rollback-confirmation-confirm": "দয়া করে নিশ্চিত করুন:", + "rollback-confirmation-yes": "পুনর্বহাল করুন", + "rollback-confirmation-no": "বাতিল করুন", "rollbacklink": "পুনর্বহাল", "rollbacklinkcount": "$1টি {{PLURAL:$1|সম্পাদনা}} রোলব্যাক করুন", "rollbacklinkcount-morethan": "$1টির বেশি {{PLURAL:$1|সম্পাদনা}} রোলব্যাক করুন", diff --git a/languages/i18n/ce.json b/languages/i18n/ce.json index 36b7f36986..d648d23671 100644 --- a/languages/i18n/ce.json +++ b/languages/i18n/ce.json @@ -665,6 +665,7 @@ "content-failed-to-parse": "Чулацам $2 богӀуш бац $1: $3.", "invalid-content-data": "Хилийта йиш йоцу хаамаш", "content-not-allowed-here": "Чулацам \"$1\" [[:$2]] агӀонгахь хилийта йиш яц", + "editwarning-warning": "Кхий агӀо схьаелича ахьа бина хийцамаш дӀабала мега.\nАхьа системехь регистраци йина елахь, хьа йиш ю «{{int:prefs-editing}}» декъехь хӀара дӀахьедар дӀадайа хьайн нисдаран гӀирс чохь.", "editpage-notsupportedcontentformat-title": "Чулацаман тайпа ловш яц", "content-model-wikitext": "вики-йоза", "content-model-text": "цхьалхе йоза", diff --git a/languages/i18n/da.json b/languages/i18n/da.json index cd90f56391..48eef87adf 100644 --- a/languages/i18n/da.json +++ b/languages/i18n/da.json @@ -1515,6 +1515,7 @@ "rcfilters-watchlist-edit-watchlist-button": "Rediger din liste med overvÃ¥gede sider", "rcfilters-watchlist-showupdated": "Ændringer til sider du ikke har besøgt siden ændringerne blev gjort vises med fed.", "rcfilters-preference-label": "Brug grænsefladesnittet uden JavaScript", + "rcfilters-preference-help": "Indlæser Seneste ændringer uden mulighed for søgning med filter eller for fremhævelse.", "rcfilters-watchlist-preference-label": "Brug grænsefladesnittet uden JavaScript", "rcfilters-watchlist-preference-help": "Indlæser overvÃ¥gningslisten uden mulighed for søgning med filter eller fremhævelse.", "rcfilters-filter-showlinkedfrom-label": "Vis ændringer pÃ¥ sider hvortil der linkes fra", @@ -3127,7 +3128,7 @@ "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2", "tag-mw-new-redirect": "Ny omdirigering", "tag-mw-removed-redirect": "Fjernede omdirigering", - "tag-mw-changed-redirect-target": "OmdigeringsmÃ¥l ændret", + "tag-mw-changed-redirect-target": "OmdirigeringsmÃ¥l ændret", "tag-mw-blank": "Sidetømning", "tag-mw-blank-description": "Redigeringer som tømmer en side", "tag-mw-replace": "Erstattet", @@ -3478,6 +3479,8 @@ "authpage-cannot-login": "Kunne ikke starte login.", "cannotauth-not-allowed-title": "Adgang nægtet", "cannotauth-not-allowed": "Du har ikke tilladelse til at bruge denne side", + "changecredentials": "Skift adgangskode", + "changecredentials-submit": "Skift adgangskode", "removecredentials": "Fjern akkreditiver", "removecredentials-submit": "Fjern akkreditiver", "removecredentials-invalidsubpage": "$1 er ikke en gyldig type for akkreditiver.", diff --git a/languages/i18n/de.json b/languages/i18n/de.json index f7c3806a60..29b2546581 100644 --- a/languages/i18n/de.json +++ b/languages/i18n/de.json @@ -95,7 +95,8 @@ "ToBeFree", "PerfektesChaos", "Kurt Jansson", - "McDutchie" + "McDutchie", + "Johanna Strodt (WMDE)" ] }, "tog-underline": "Links unterstreichen:", @@ -141,7 +142,8 @@ "tog-norollbackdiff": "Unterschiede nach dem Zurücksetzen nicht anzeigen", "tog-useeditwarning": "Warnen, sofern eine zur Bearbeitung geöffnete Seite verlassen wird, die nicht gespeicherte Änderungen enthält", "tog-prefershttps": "Immer eine sichere Verbindung benutzen, solange ich angemeldet bin", - "tog-showrollbackconfirmation": "Beim Klicken auf einen Zurücksetzen-Link eine Bestätigungsaufforderung anzeigen", + "tog-showrollbackconfirmation": "Bei Klick auf „kommentarlos zurücksetzen“ eine Sicherheitsabfrage anzeigen", + "tog-showrollbackconfirmation-prerelease-warning": "Bitte beachten: Diese Funktion steht noch nicht zur Verfügung. Du kannst aber bereits jetzt definieren, ob du eine Sicherheitsabfrage haben möchtest. Diese Einstellung wird dann bei der [https://de.wikipedia.org/wiki/Wikipedia:Technische_Wünsche/Topwünsche/Sicherheitsabfrage#Status späteren Bereitstellung] der Funktion berücksichtigt.", "underline-always": "immer", "underline-never": "nie", "underline-default": "abhängig von der Benutzeroberfläche oder Browsereinstellung", @@ -2342,7 +2344,7 @@ "deleting-backlinks-warning": "Warnung: Es verweisen noch [[Special:WhatLinksHere/{{FULLPAGENAME}}|andere Seiten]] auf diese zu löschende Seite oder sie ist noch an anderer Stelle eingebunden.", "deleting-subpages-warning": "Warnung: Die Seite, die du löschen möchtest, hat [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|eine Unterseite|$1 Unterseiten|51=über 50 Unterseiten}}]].", "rollback": "Zurücksetzen der Änderungen", - "rollback-confirmation-confirm": "{{PLURAL:$1|0=Diese Bearbeitungen|Eine Bearbeitung|$1 Bearbeitungen}} zurücksetzen?", + "rollback-confirmation-confirm": "Bitte bestätigen:", "rollback-confirmation-yes": "Zurücksetzen", "rollback-confirmation-no": "Abbrechen", "rollbacklink": "Zurücksetzen", diff --git a/languages/i18n/diq.json b/languages/i18n/diq.json index c17a6bbdee..366a783d91 100644 --- a/languages/i18n/diq.json +++ b/languages/i18n/diq.json @@ -2076,6 +2076,7 @@ "delete-warning-toobig": "no pel wayirê tarixê vurnayiş ê derg o, $1 {{PLURAL:$1|revizyonê|revizyonê}} seri de.\nhewn a kerdışê ıney {{SITENAME}} şuxul bıne gırano;\nbı diqqet dewam kerê.", "deleteprotected": "Şıma nêşenê ena perer esternê, çıkı per starya ya.", "rollback": "vurnayişan tepiya bıger", + "rollback-confirmation-no": "Bıtexelne", "rollbacklink": "ageyrayış", "rollbacklinkcount": "$1 {{PLURAL:$1|vurnayış|vurnayışi}} peyd gıroti", "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|vurnayış|vuranyışi}} tewr peyd gırot", diff --git a/languages/i18n/et.json b/languages/i18n/et.json index 6f32bc033e..a8515a946c 100644 --- a/languages/i18n/et.json +++ b/languages/i18n/et.json @@ -78,6 +78,7 @@ "tog-norollbackdiff": "Ära näita erinevusi pärast tühistamist", "tog-useeditwarning": "Hoiata mind, kui lahkun redigeerimisleheküljelt muudatusi salvestamata", "tog-prefershttps": "Kasuta sisselogimisel alati turvalist ühendust", + "tog-showrollbackconfirmation": "Küsi tühistamislingile klõpsamise järel kinnitust", "underline-always": "Alati", "underline-never": "Mitte kunagi", "underline-default": "Kujunduse või brauseri vaikeväärtus", @@ -463,6 +464,7 @@ "badretype": "Sisestatud paroolid ei lange kokku.", "usernameinprogress": "Selle kasutajanimega konto loomine on juba pooleli.\nPalun oota.", "userexists": "Sisestatud kasutajanimi on juba kasutusel.\nPalun valige uus nimi.", + "createacct-normalization": "Tehniliste piirangute tõttu kohandatakse sinu kasutajanimi kujule \"$2\".", "loginerror": "Viga sisselogimisel", "createacct-error": "Tõrge konto loomisel", "createaccounterror": "Kasutajakonto loomine ebaõnnestus: $1", @@ -2271,6 +2273,9 @@ "deleting-backlinks-warning": "Hoiatus: [[Special:WhatLinksHere/{{FULLPAGENAME}}|Teised leheküljed]] viitavad leheküljele, mida oled kustutamas, või see lehekülg on kasutuses mallina.", "deleting-subpages-warning": "Hoiatus: Oled kustutamas lehekülge, millel on [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|alamlehekülg|$1 alamlehekülge|51=üle 50 alamlehekülje}}]].", "rollback": "Tühista muudatused", + "rollback-confirmation-confirm": "Palun kinnita:", + "rollback-confirmation-yes": "Tühista", + "rollback-confirmation-no": "Loobu", "rollbacklink": "tühista", "rollbacklinkcount": "tühista {{PLURAL:$1|üks muudatus|$1 muudatust}}", "rollbacklinkcount-morethan": "tühista üle {{PLURAL:$1|ühe muudatuse|10 muudatuse}}", @@ -2477,6 +2482,8 @@ "ipb-confirm": "Kinnita blokeering", "ipb-sitewide": "Saidiülene", "ipb-partial": "Osaline", + "ipb-sitewide-help": "Viki kõigi lehekülgede muutmine ja kogu ülejäänud kaastöö.", + "ipb-partial-help": "Teatud lehekülgede või nimeruumide muutmine.", "ipb-pages-label": "Leheküljed", "ipb-namespaces-label": "Nimeruumid", "badipaddress": "Vigane IP-aadress", @@ -3055,6 +3062,7 @@ "confirm-unwatch-top": "Kas eemaldad selle lehekülje oma jälgimisloendist?", "confirm-rollback-button": "Sobib", "confirm-rollback-top": "Kas tühistad sellel leheküljel tehtud muudatused?", + "confirm-rollback-bottom": "See toiming tühistab koheselt valitud muudatused sellel leheküljel.", "confirm-mcrrestore-title": "Redaktsiooni taastamine", "confirm-mcrundo-title": "Muudatuse eemaldamine", "mcrundofailed": "Eemaldamine ebaõnnestus", @@ -3440,7 +3448,7 @@ "logentry-rights-autopromote": "$1 {{GENDER:$2|viidi}} automaatselt üle teise rühma; enne oli $4, nüüd on $5", "logentry-upload-upload": "$1 {{GENDER:$2|laadis üles}} faili $3", "logentry-upload-overwrite": "$1 {{GENDER:$2|laadis üles}} uue versiooni failist $3", - "logentry-upload-revert": "$1 {{GENDER:$2|laadis üles}} faili $3", + "logentry-upload-revert": "$1 {{GENDER:$2|taastas}} faili $3 vanema versiooni", "log-name-managetags": "Märgiste haldamise logi", "log-description-managetags": "Sellel leheküljel on toodud [[Special:Tags|märgiste]] haldamisega seotud tegevused. Logis on ainult toimingud, mida administraatorid on teinud käsitsi. Siin puuduvad logisissekanded viki tarkvaras koostatud või sealt kustutatud märgiste kohta.", "logentry-managetags-create": "$1 {{GENDER:$2|koostas}} märgise \"$4\"", @@ -3675,6 +3683,7 @@ "log-action-filter-suppress-reblock": "Kasutaja varjamine taasblokeerimise teel", "log-action-filter-upload-upload": "Uus üleslaadimine", "log-action-filter-upload-overwrite": "Uuesti üleslaadimine", + "log-action-filter-upload-revert": "Taastamine", "authmanager-authn-not-in-progress": "Autentimine pole teoksil või seansiandmed läksid kaduma. Palun alusta uuesti.", "authmanager-authn-no-primary": "Ette antud autentimisandmeid ei õnnestunud autentida.", "authmanager-authn-no-local-user": "Ette antud autentimisandmed pole selles vikis seotud ühegi kasutajaga.", @@ -3775,6 +3784,8 @@ "passwordpolicies-policy-maximalpasswordlength": "Parool peab olema $1 {{PLURAL:$1|märgist}} lühem.", "passwordpolicies-policy-passwordcannotbepopular": "Parool ei tohi olla {{PLURAL:$1|populaarne parool|$1 populaarse parooli loendis}}.", "passwordpolicies-policy-passwordnotinlargeblacklist": "Parool ei saa olla 100 000 kõige levinuma parooli loendis.", + "passwordpolicies-policyflag-forcechange": "peab muutma sisselogimisel", + "passwordpolicies-policyflag-suggestchangeonlogin": "soovita muutmist sisselogimisel", "easydeflate-invaliddeflate": "Ette antud sisu ei ole õigesti vähendatud", "unprotected-js": "Turvalisuse huvides ei saa JavaScripti laadida kaitsmata lehekülgedelt. Palun koosta JavaScripti ainult nimeruumis MediaWiki või kasutajate nimeruumi alamleheküljel." } diff --git a/languages/i18n/exif/diq.json b/languages/i18n/exif/diq.json index 03fc793c32..53e0389ea6 100644 --- a/languages/i18n/exif/diq.json +++ b/languages/i18n/exif/diq.json @@ -129,7 +129,7 @@ "exif-gpsdifferential": "GPS differential correction", "exif-coordinate-format": "$1° $2′ $3″ $4", "exif-jpegfilecomment": "Vatışê dosyada JPEG'i", - "exif-keywords": "Qesa kelimey", + "exif-keywords": "Çekuyên kıliti", "exif-worldregioncreated": "Mıntıqaya dınyaya ke tede resım gêriyayayo", "exif-countrycreated": "Dewleta ke tede resım gêriyayayo", "exif-countrycodecreated": "Kodê dewleta ke tede resım anciyayo", diff --git a/languages/i18n/exif/ja.json b/languages/i18n/exif/ja.json index 0c5ed5420c..353ad9c82b 100644 --- a/languages/i18n/exif/ja.json +++ b/languages/i18n/exif/ja.json @@ -208,11 +208,12 @@ "exif-compression-34712": "JPEG2000", "exif-copyrighted-true": "著作権あり", "exif-copyrighted-false": "著作権情報未設定", - "exif-photometricinterpretation-0": "黒と白(白が0です)", + "exif-photometricinterpretation-0": "黒と白(白が0)", "exif-photometricinterpretation-1": "黒と白(黒が0)", "exif-photometricinterpretation-2": "RGB", - "exif-photometricinterpretation-3": "Palette", - "exif-photometricinterpretation-5": "Separated (Probably CMYK)", + "exif-photometricinterpretation-3": "パレット", + "exif-photometricinterpretation-4": "透明マスク", + "exif-photometricinterpretation-5": "分版 (おそらく CMYK)", "exif-photometricinterpretation-6": "YCbCr", "exif-photometricinterpretation-8": "CIE L*a*b*", "exif-photometricinterpretation-9": "CIE L*a*b* (ICC エンコード)", diff --git a/languages/i18n/fa.json b/languages/i18n/fa.json index 47a0c87029..f7fd250b4c 100644 --- a/languages/i18n/fa.json +++ b/languages/i18n/fa.json @@ -72,7 +72,8 @@ "Physicsch", "Nbi", "Amjad Khan", - "Ahmad252" + "Ahmad252", + "FarsiNevis" ] }, "tog-underline": "خط کشیدن زیر پیوندها:", @@ -118,6 +119,8 @@ "tog-norollbackdiff": "بعد از واگردانی، تفاوت نشان داده نشود", "tog-useeditwarning": "زمان خروج از صفحهٔ ویرایش در صورت داشتن ویرایش‌های‌ ذخیره‌نشده به من هشدار داده شود", "tog-prefershttps": "هنگامی که به سامانه وارد شده‌ام، همواره از اتصال امن استفاده شود", + "tog-showrollbackconfirmation": "نمایش یک هشدار در هنگام کلیک بر دکمه واگردانی", + "tog-showrollbackconfirmation-prerelease-warning": "لطفا توجه کنید که این قابلیت هنوز در دسترس نیست. اگر این قابلیت را فعال کنید انتخابتان برای [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status زمانی که قابلیت فعال شود] ذخیره خواهد شد.", "underline-always": "همیشه", "underline-never": "هرگز", "underline-default": "پیش‌فرض پوسته یا مرورگر", @@ -226,6 +229,7 @@ "returnto": "بازگشت به $1.", "tagline": "از {{SITENAME}}", "help": "راهنما", + "help-mediawiki": "راهنما درباره مدیاویکی", "search": "جستجو", "search-ignored-headings": "#
    \n# سر‌فصل‌هایی که توسط جستجو نادیده گرفته خواهندشد.‌\n# تأثیر تغییرات زمانی رخ می‌دهد که صفحهٔ حاوی آن سرفصل، نمایه شود.\n# شما می‌توانید با انجام یک ویرایش پوچ صفحه را وادار به دوباره نمایه‌شدن کنید.\n# نحو به شرح زیر است:\n#  *هر چه از یک نویسهٔ «#» تا آخر خط بیاید، یک توضیح است.\n#  *هر خط بدون فاصله، دقیقاً عنوانی است که نادیده گرفته می‌شود (با رعایت بزرگی و کوچکی حروف).\nمنابع\nپیوند به بیرون\nهمچنین ببینید\n#
    ", "searchbutton": "جستجو", @@ -504,6 +508,7 @@ "badretype": "گذرواژه‌هایی که وارد کرده‌اید یکسان نیستند.", "usernameinprogress": "ایجاد حساب برای این نام کاربر در جریان است. لطفا صبور باشید.", "userexists": "نام کاربری‌ای که وارد کردید قبلاً استفاده شده‌است.\nلطفاً یک نام دیگر انتخاب کنید.", + "createacct-normalization": "نام کاربری شما به دلایل فنی به «$2» تبدیل خواهد شد.", "loginerror": "خطا در ورود به سامانه", "createacct-error": "خطای ایجاد حساب کاربری", "createaccounterror": "امکان ساختن این حساب وجود ندارد: $1", @@ -617,7 +622,7 @@ "resetpass-abort-generic": "تغییر گذرواژه به دست یکی از افزونه‌ها لغو شده است.", "resetpass-expired": "رمز عبور شما منقضی شده‌است. لطفاً برای ورود رمز عبور جدیدی را تنظیم کنید.", "resetpass-expired-soft": "رمز عبور شما منقضی شده‌است و نیاز به تغییر دارد. لطفاً اکنون رمز عبور جدیدی را انتخاب کنید، یا برای تغییر آن در آینده، دکمهٔ «{{int:authprovider-resetpass-skip-label}}» را کلیک کنید.", - "resetpass-validity": "لطفا برای ورود گذرواژه جدیدی را انتخاب کنید", + "resetpass-validity": "گذرواژه شما معتبر نیست: $1\n\nلطفا برای ورود گذرواژه جدیدی را انتخاب کنید.", "resetpass-validity-soft": "گذرواهٔ شما صحیح نیست: $1\n\nلطفاً یک گذرواژهٔ تازه الآن انتخاب کنید یا بر «{{int:authprovider-resetpass-skip-label}}» کلیک کنید که دوباره آن را بعداً تغییر دهید.", "passwordreset": "بازنشانی گذرواژه", "passwordreset-text-one": "برای بازنشانی گذرواژه‌تان این فرم را کامل کنید.", @@ -701,7 +706,8 @@ "subject-preview": "پیش‌نمایش موضوع:", "previewerrortext": "در زمان تلاش برای نمایش دادن تغییرات شما، خطایی رخ داد.", "blockedtitle": "کاربر بسته شده‌است", - "blockedtext-partial": "حساب کاربری یا آدرس آی‌پی شما از انجام تغییرات در این صفحه منع شده‌است. شما همچنان می‌توانید در دیگر صفحه‌های این ویکی ویرایش کنید. برای مشاهده جزئیات کامل قطع دسترسی به [[ویژه:مشارکت‌های من|مشارکت‌های حساب]] مراجعه کنید.\n\nاین قطع دسترسی توسط $1 انجام گرفته‌است.\n\nدلیل قطع دسترسی $2 است.\n\n* زمان آغاز قطع دسترسی: $8\n* زمان پایان قطع دسترسی: $6\n* موارد مورد نظر: $7\n* شناسه قطع دسترسی: #$5", + "blocked-email-user": "دسترسی حساب کاربری شما از ارسال ایمیل قطع شده است. شما همچنان می‌توانید سایر صفحات این ویکی را ویرایش کنید.می‌توانید جزئیات کامل قطع دسترسی را در [[Special:MyContributions|مشارکت‌های حساب]] ببینید.\n\nقطع دسترسی توسط $1 انجام شده است.\n\nدلیل داده‌شده $2 بوده است.\n\n* شروع قطع دسترسی: $8\n* اتمام قطع دسترسی: $6\n* هدف قطع دسترسی: $7\n* شناسه قطع دسترسی #$5", + "blockedtext-partial": "حساب کاربری یا آدرس آی‌پی شما از انجام تغییرات در این صفحه منع شده‌است. شما همچنان می‌توانید در دیگر صفحه‌های این ویکی ویرایش کنید. برای مشاهده جزئیات کامل قطع دسترسی به [[Special:MyContributions|مشارکت‌های حساب]] مراجعه کنید.\n\nاین قطع دسترسی توسط $1 انجام گرفته‌است.\n\nدلیل قطع دسترسی $2 است.\n\n* زمان آغاز قطع دسترسی: $8\n* زمان پایان قطع دسترسی: $6\n* موارد مورد نظر: $7\n* شناسه قطع دسترسی: #$5", "blockedtext": "دسترسی حساب کاربری یا نشانی آی‌پی شما بسته شده‌است.\n\nاین قطع دسترسی توسط $1 انجام شده است.\nدلیل ارائه‌شده چنین است: $2\n\n* شروع قطع دسترسی: $8\n* پایان قطع دسترسی: $6\n* کاربری هدف قطع دسترسی: $7\n\nشما می‌توانید با $1 یا [[{{MediaWiki:Grouppage-sysop}}|مدیری]] دیگر تماس بگیرید و در این باره صحبت کنید.\nتوجه کنید که شما نمی‌توانید از قابلیت «{{int:emailuser}}» استفاده کنید مگر آنکه آدرس ایمیل معتبری در [[Special:Preferences|ترجیحات کاربری]] خودتان ثبت کرده باشید و نیز باید امکان استفاده از این قابلیت برای شما قطع نشده باشد.\nنشانی آی‌پی فعلی شما $3 و شمارهٔ قطع دسترسی شما $5 است.\nلطفاً تمامی جزئیات فوق را در کلیهٔ درخواست‌هایی که در این باره مطرح می‌کنید ذکر کنید.", "autoblockedtext": "دسترسی نشانی آی‌پی شما قطع شده‌است، زیرا این نشانی آی‌پی توسط کاربر دیگری استفاده شده که دسترسی او توسط $1 قطع شده‌است.\nدلیل ارائه‌شده چنین است:\n\n:''$2''\n\n* شروع قطع دسترسی: $8\n* پایان قطع دسترسی: $6\n* کاربری هدف قطع دسترسی: $7\n\nشما می‌توانید با $1 یا [[{{MediaWiki:Grouppage-sysop}}|مدیری]] دیگر تماس بگیرید و در این باره صحبت کنید.\nتوجه کنید که شما نمی‌توانید از قابلیت «{{int:emailuser}}» استفاده کنید مگر آنکه نشانی ایمیل معتبری در [[Special:Preferences|ترجیحات کاربری]] خودتان ثبت کرده باشید و نیز باید امکان استفاده از این قابلیت برای شما قطع نشده باشد.\nنشانی آی‌پی فعلی شما $3 و شمارهٔ قطع دسترسی شما $5 است.\nلطفاً تمامی جزئیات فوق را در کلیهٔ درخواست‌هایی که در این باره مطرح می‌کنید ذکر کنید.", "systemblockedtext": "نام کاربری یا نشانی آی‌پی شما خودکار توسط مدیاویکی مسدود شده‌است.\nدلیل ارائه‌شده:\n\n:$2\n\n* آغاز بلاک: $8\n* پایان بلاک: $6\n* قطع دسترسی‌شده مورد نظر: $7\n\nنشانی آی‌پی کنونی شما $3 است.\nخواهشمند است تمام جزئیات بالا را در هر پرس‌وجویی که انجام می‌دهید قرار دهید.", @@ -787,6 +793,9 @@ "edit-gone-missing": "امکان به‌روز کردن صفحه وجود ندارد.\nبه نظرمی‌رسد که صفحه حذف شده باشد.", "edit-conflict": "تعارض ویرایشی.", "edit-no-change": "ویرایش شما نادیده گرفته شد، زیرا تغییری در متن داده نشده بود.", + "edit-slots-cannot-add": "این {{PLURAL:$1|اسلات|اسلات‌ها}} پشتیبانی نمی‌شود: $2.", + "edit-slots-cannot-remove": "امکان حذف این {{PLURAL:$1|اسلات|اسلات‌ها}} وجود ندارد: $2.", + "edit-slots-missing": "این {{PLURAL:$1|اسلات|اسلات‌ها}} ناموجود است: $2.", "postedit-confirmation-created": "صفحه ایجاد شده است.", "postedit-confirmation-restored": "صفحه بازیابی شده است.", "postedit-confirmation-saved": "ویرایش شما ذخیره شد.", @@ -795,7 +804,7 @@ "defaultmessagetext": "متن پیش‌فرض پیغام", "content-failed-to-parse": "عدم موفقیت در تجزیه محتوای $2 برای مدل $1: $3", "invalid-content-data": "داده محتوای نامعتبر", - "content-not-allowed-here": "محتوای «$1» در صفحهٔ [[:$2]] مجاز نیست", + "content-not-allowed-here": "محتوای «$1» در صفحهٔ [[:$2]] بخش «$3» مجاز نیست", "editwarning-warning": "خروج از این صفحه ممکن است باعث شود که شما هر شانسی که به وجود آورده‌اید را از دست بدهید.\nاگر شما وارد سامانه شده‌اید، می‌توانید این هشدار را در بخش «{{int:prefs-editing}}» ترجیحاتتان غیرفعال کنید.", "editpage-invalidcontentmodel-title": "مدل محتوای پشتیبانی نشده", "editpage-invalidcontentmodel-text": "مدل محتوای «$1» پشتیبای نمی‌شود.", @@ -1154,6 +1163,7 @@ "prefs-displayrc": "گزینه‌های نمایش", "prefs-displaywatchlist": "گزینه‌های نمایش", "prefs-changesrc": "نمایش تغییرات", + "prefs-changeswatchlist": "نمایش تغییرات", "prefs-pageswatchlist": "صفحه‌های پی‌گیری‌شده", "prefs-tokenwatchlist": "بلیط", "prefs-diffs": "تفاوت‌ها", @@ -1304,7 +1314,7 @@ "grant-delete": "حذف صفحات، نسخه‌های ویرایش و سیاهه ورودی", "grant-editinterface": "ویرایش صفحه‌های جی‌سان کاربری یا سراسری و فضای نام مدیاویکی", "grant-editmycssjs": "ویرایش CSS /جاوااسکریپت/JSON کاربری", - "grant-editmyoptions": "اولویت‌های کاربری را ویرایش کنید", + "grant-editmyoptions": "اولویت‌های کاربری و پیکربندی JSON را ویرایش کنید", "grant-editmywatchlist": "ویرایش فهرست پی‌گیری‌هایتان", "grant-editsiteconfig": "ویرایش گسترده CSS/JS کاربر", "grant-editpage": "ویرایش صفحات موجود", @@ -1523,10 +1533,10 @@ "rcfilters-watchlist-markseen-button": "نشانه‌گذاری تمام تغییرات به‌عنوان خوانده‌شده", "rcfilters-watchlist-edit-watchlist-button": "ویرایش فهرست صفحه‌های پی‌گیری‌هایتان", "rcfilters-watchlist-showupdated": "تغییرات صفحاتی که شما از زمانی که تغییر بازدیدشان نکرده‌اید به صورت پررنگ و با نشانگر توپر نمایش می‌یابد.", - "rcfilters-preference-label": "مخفی کردن نسخه بهبود یافته تغییرات اخیر", - "rcfilters-preference-help": "تغییرات رابط کاربری که در سال ۲۰۱۷ اضافه شده است را بر می‌گرداند.", + "rcfilters-preference-label": "استفاده واسط بدون جاوااسکریپت", + "rcfilters-preference-help": "نمایش تغییرات اخیر بدون پالایه‌های جستجو یا قابلیت پررنگ کردن.", "rcfilters-watchlist-preference-label": "استفاده واسط بدون جاوااسکریپت", - "rcfilters-watchlist-preference-help": "واگردان در سال ۲۰۱۷ دوباره طراحی شد و تمام ابزارها اضافه و از آن زمان به بعد اضافه شدند.", + "rcfilters-watchlist-preference-help": "نمایش فهرست پی‌گیری بدون پالایه‌های جستجو یا قابلیت پررنگ کردن.", "rcfilters-filter-showlinkedfrom-label": "نمایش تغییرات صفحاتی که پیوند شده‌اند", "rcfilters-filter-showlinkedfrom-option-label": "صفحات پیوند به صفحهٔ انتخاب شده", "rcfilters-filter-showlinkedto-label": "نمایش تغییرات در صفحاتی که در ون این صفحه پیوند شده‌اند", @@ -2028,6 +2038,7 @@ "move": "انتقال", "movethispage": "انتقال این صفحه", "unusedimagestext": "پرونده‌های زیر موجودند اما در هیچ صفحه‌ای به کار نرفته‌اند.\nلطفاً توجه داشته باشید که دیگر وبگاه‌ها ممکن است با یک نشانی اینترنتی مستقیم به یک پرونده پیوند دهند، و با وجود این که در استفادهٔ فعال هستند در این جا فهرست شوند.", + "unusedimagestext-categorizedimgisused": "فایل موردنظر موجود است اما در هیچ صفحه‌ای استفاده نشده است. تصاویر رده‌بندی‌شده حتی اگر در هیچ صفحه‌ای درج نشده باشند، به عنوان تصاویر مورداستفاده محسوب می‌شوند.\nلطفا توجه داشته باشید که دیگر وب‌سایت‌ها ممکن است به صورت مستقیم به یک فایل پیوند داده باشند و با وجود اینکه آن فایل فعال محسوب می‌شود در اینجا لیست شده باشد.", "unusedcategoriestext": "این رده‌ها وجود دارند ولی هیچ مقاله یا ردهٔ دیگری از آنها استفاده نمی‌کند.", "notargettitle": "مقصدی نیست", "notargettext": "شما صفحهٔ یا کاربر مقصدی برای انجام این عمل روی آن مشخص نکرده‌اید.", @@ -2307,6 +2318,9 @@ "deleting-backlinks-warning": "هشدار: [[Special:WhatLinksHere/{{FULLPAGENAME}}|صفحه‌های دیگری]] هستند که به صفحه‌ای که شما در حال حذف آن هستید پیوند دارند یا آن را تراگنجانیده‌اند.", "deleting-subpages-warning": "هشدار: صفحه‌ای که شما می‌خواهید حذف کنید [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|یک زیرصفحه|$1 زیرصفحه|51=بیش از پنجاه زیرصفحه}}]] دارد.", "rollback": "واگردانی ویرایش‌ها", + "rollback-confirmation-confirm": "لطفاً تأیید کنید:", + "rollback-confirmation-yes": "واگردانی", + "rollback-confirmation-no": "انصراف", "rollbacklink": "واگردانی", "rollbacklinkcount": "واگردانی $1 ویرایش", "rollbacklinkcount-morethan": "واگردانی بیش از $1 ویرایش", @@ -2448,6 +2462,7 @@ "anoncontribs": "مشارکت‌ها", "contribsub2": "برای {{GENDER:$3|$1}} ($2)", "contributions-userdoesnotexist": "حساب کاربری «$1» ثبت نشده‌است.", + "negative-namespace-not-supported": "فضاهای نام با مقدار منفی پشتیبانی نمی‌شوند.", "nocontribs": "هیچ تغییری با این مشخصات یافت نشد.", "uctop": "نسخهٔ کنونی", "month": "در این ماه (و پیش از آن):", @@ -2528,7 +2543,9 @@ "ipb-blocklist": "دیدن قطع دسترسی‌های موجود", "ipb-blocklist-contribs": "مشارکت‌های $1", "ipb-blocklist-duration-left": "$1 باقی مانده", + "block-actions": "اقدامات قطع دسترسی شده:", "block-expiry": "زمان سرآمدن:", + "block-options": "گزینه‌های بیشتر:", "block-prevent-edit": "در حال ویرایش", "block-reason": "دلیل:", "block-target": "نام کاربری یا آدرس آی‌پی:", @@ -2570,6 +2587,7 @@ "emailblock": "ایمیل بسته‌شده", "blocklist-nousertalk": "نمی‌تواند صفحهٔ بحث خود را ویرایش کند", "blocklist-editing": "در حال ویرایش", + "blocklist-editing-sitewide": "ویرایش (کل ویکی)", "blocklist-editing-page": "صفحات", "blocklist-editing-ns": "فضاهای نام", "ipblocklist-empty": "فهرست بسته‌شدن‌ها خالی‌است.", @@ -2599,6 +2617,7 @@ "ipb_expiry_old": "زمان سرآمدن در گذشته‌است.", "ipb_expiry_temp": "قطع دسترسی کاربرهای پهنان باید همیشگی باشد.", "ipb_hide_invalid": "قادر به سرکوب این حساب نیست; این بیشتر از {{PLURAL:$1|یک ویرایش|$1 ویرایش‌ها}} دارد.", + "ipb_hide_partial": "قطع دسترسی با پنهان کردن نام کاربری حتما باید یک قطع دسترسی سراسری باشد", "ipb_already_blocked": "«$1» همین الان هم بسته‌است", "ipb-needreblock": "دسترسی $1 از قبل بسته است. آیا می‌خواهید تنظیمات آن را تغییر دهید؟", "ipb-otherblocks-header": "سایر {{PLURAL:$1|قطع دسترسی‌ها|قطع دسترسی‌ها}}", @@ -2655,9 +2674,11 @@ "move-watch": "پی‌گیری صفحه‌های مبدأ و مقصد", "movepagebtn": "صفحه منتقل شود", "pagemovedsub": "انتقال با موفقیت انجام شد", + "cannotmove": "این صفحه قابل انتقال نیست به {{PLURAL:$1|دلیل|دلایل}}:", "movepage-moved": "'''«$1» به «$2» منتقل شد'''", "movepage-moved-redirect": "یک تغییرمسیر ایجاد شد.", "movepage-moved-noredirect": "از ایجاد تغییرمسیر ممانعت شد.", + "movepage-delete-first": "صفحه مقصد تعداد زیادی نسخه برای حذف دارد. لطفا ابتدا صفحه را دستی حذف کنید و سپس دوباره سعی کنید.", "articleexists": "صفحه‌ای با این نام از قبل وجود دارد، یا نامی که انتخاب کرده‌اید معتبر نیست.\nلطفاً نام دیگری انتخاب کنید.", "cantmove-titleprotected": "شما نمی‌توانید صفحه را به این نشانی انتقال دهید، چرا که عنوان جدید در برابر ایجاد محافظت شده‌است", "movetalk": "صفحهٔ بحث هم منتقل شود", @@ -2941,6 +2962,7 @@ "pageinfo-category-files": "تعداد پرونده‌ها", "pageinfo-user-id": "شناسه کاربر", "pageinfo-file-hash": "مقدار هش", + "pageinfo-view-protect-log": "نمایش سیاهه محفاظت برای این صفحه.", "markaspatrolleddiff": "برچسب گشت بزن", "markaspatrolledtext": "به این صفحه برچسب گشت بزن", "markaspatrolledtext-file": "انتخاب این نسخهٔ پرونده به عنوان گشت خورده", @@ -3077,9 +3099,13 @@ "confirm-unwatch-top": "این صفحه از فهرست پی‌گیری‌های شما حذف شود؟", "confirm-rollback-button": "باشد", "confirm-rollback-top": "خنثی‌سازی ویرایش‌های این صفحه؟", + "confirm-rollback-bottom": "این عمل به سرعت تغییرات انتخاب‌شده در صفحه را واگردانی خواهد کرد.", "confirm-mcrrestore-title": "بازیابی نسخه", "confirm-mcrundo-title": "خنثی کردن تغییرات", "mcrundofailed": "واگردانی ناموفق بود", + "mcrundo-missingparam": "فقدان پارامترهای ضروری در درخواست", + "mcrundo-changed": "این صفحه از زمانی که شما تفاوت را دیده‌اید تغییر کرده است. تغییر جدید را مشاهده کنید.", + "mcrundo-parse-failed": "قادر به تجزیه نسخه جدید نیست: $1", "semicolon-separator": "؛ ", "comma-separator": "، ", "percent": "$1٪", @@ -3284,6 +3310,7 @@ "specialpages-group-developer": "ابزارهای توسعه‌دهندگان", "blankpage": "صفحهٔ خالی", "intentionallyblankpage": "این صفحه به طور عمدی خالی گذاشته شده است.", + "disabledspecialpage-disabled": "این صفحه توسط یک مدیر غیرفعال شده‌است.", "external_image_whitelist": " #این سطر را همان‌گونه که هست رها کنید
    \n#عبارت‌های باقاعده (regex) را در زیر قرار دهید (فقط بخشی که بین // قرار می‌گیرد)\n#آن‌ها با نشانی اینترنتی تصاویر خارجی پیوند داده شده تطبیق داده می‌شوند\n#مواردی که مطابق باشند به صورت تصویر نمایش می‌یابند، و در غیر این صورت تنها یک پیوند به تصویر نمایش می‌یابد\n#سطرهایی که با # آغاز شوند به عنوان توضیحات در نظر گرفته می‌شوند\n#این سطرها به کوچکی و بزرگی حروف حساس هستند\n\n#عبارت‌های باقاعده (regex)  را زیر این سطر قرار دهید. این سطر را همان‌گونه که هست رها کنید
    ", "tags": "برچسب‌های تغییر مجاز", "tag-filter": "پالایش [[Special:Tags|برچسب‌ها]]:", @@ -3471,6 +3498,12 @@ "logentry-block-block": "$1 {{GENDER:$4|$3}} را $5 {{GENDER:$2|بست}} $6", "logentry-block-unblock": "$1 {{GENDER:$4|$3}} را {{GENDER:$2|بازکرد}}", "logentry-block-reblock": "$1 {{GENDER:$2|تنظیمات}} بستن {{GENDER:$4|$3}} را به پایان قطع دسترسی $5 $6 تغییر داد.", + "logentry-partialblock-block-page": "{{PLURAL:$1|صفحه|صفحات}} $2", + "logentry-partialblock-block-ns": "{{PLURAL:$1|فضای نام|فضاهای نام}} $2", + "logentry-partialblock-block": "$1 {{GENDER:$4|$3}} را از ویرایش $7 با انقضای $5 $6 {{GENDER:$2|قطع دسترسی کرد}}", + "logentry-partialblock-reblock": "$1 {{GENDER:$2|تنظیمات}} بستن {{GENDER:$4|$3}} را به جلوگیری از ویرایش $7 و پایان قطع دسترسی $5 $6 تغییر داد", + "logentry-non-editing-block-block": "$1 {{GENDER:$4|$3}} را از اعمال مشخص‌شده غیرویرایشی با انقضای $5 $6 {{GENDER:$2|قطع دسترسی کرد}}", + "logentry-non-editing-block-reblock": "$1 {{GENDER:$2|تنظیمات}} بستن {{GENDER:$4|$3}} را به جلوگیری از اعمال مشخص‌شده غیرویرایشی و پایان قطع دسترسی $5 $6 تغییر داد.", "logentry-suppress-block": "$1 {{GENDER:$2|بسته شد}} {{GENDER:$4|$3}} با پایان قطع دسترسی در زمان $5 $6", "logentry-suppress-reblock": "$1 {{GENDER:$2|تنظیمات}} بستن برای {{GENDER:$4|$3}} به پایان قطع دسترسی $5 $6 تغییر یافت", "logentry-import-upload": "$1 $3 را توسط بارگذار پرونده {{GENDER:$2|درون‌ریزی کرد}}", @@ -3500,7 +3533,7 @@ "logentry-rights-autopromote": "$1 به طور خودکار از $4 به $5 {{GENDER:$2|ارتقاء یافت}}", "logentry-upload-upload": "$1 $3 را {{GENDER:$2|بارگذاری کرد}}", "logentry-upload-overwrite": "$1 نسخهٔ تازه‌ای از $3 را {{GENDER:$2|بارگذاری کرد}}", - "logentry-upload-revert": "$1 {{GENDER:$2|بارگذاری کرد}} $3", + "logentry-upload-revert": "$1 $3 را به نسخه قدیمی {{GENDER:$2|واگردانی کرد}}", "log-name-managetags": "تاریخچه مدیریت برچسب", "log-description-managetags": "این صفحه امور مدیریتی مربوط به [[Special:Tags|برچسب‌ها]] را فهرست می‌کند. سیاهه فقط حاوی فعالیت‌هایی است که توسط یک مدیر به صورت دستی انجام شده‌اند؛ برچسب‌ها ممکن است توسط نرم‌افزار ویکی ساخته یا حذف بشوند بدون اینکه هیچ ورودی در این سیاهه ثبت گردد.", "logentry-managetags-create": "$1 برچسب «$4» را {{GENDER:$2|ایجاد کرد}}", @@ -3833,6 +3866,9 @@ "passwordpolicies-policy-passwordcannotmatchblacklist": "گذرواژه نمی‌تواند مشابه گذرواژه‌های فهرست شده در فهرست سیاه باشد", "passwordpolicies-policy-maximalpasswordlength": "گذرواژه باید کمتر از $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد", "passwordpolicies-policy-passwordcannotbepopular": "گذرواژه نمی‌تواند {{PLURAL:$1|گذرواژه پراستفاده باشد|در فهرست $1 گذرواژه‌های پراستفاده باشد}}", + "passwordpolicies-policy-passwordnotinlargeblacklist": "گذرواژه نمی‌تواند یکی از ۱۰۰٬۰۰۰ گذرواژه پراستفاده باشد.", + "passwordpolicies-policyflag-forcechange": "در هنگام ورود باید تغییر دهید", + "passwordpolicies-policyflag-suggestchangeonlogin": "در هنگام ورود، پیشنهاد تغییر بده", "easydeflate-invaliddeflate": "محتوی تهیه‌شده به صورت درست خالی نشده‌است", "unprotected-js": "به دلایل امنیتی، جاوااسکریپت نمی‌تواند از صفحات محافظت‌نشده بارگیری شود. لطفا جاوااسکریپت را تنها در فضای نام مدیاویکی: و یا در زیرصفحهٔ کاربری خودتان ایجاد کنید." } diff --git a/languages/i18n/fr.json b/languages/i18n/fr.json index e232795344..fb989a556d 100644 --- a/languages/i18n/fr.json +++ b/languages/i18n/fr.json @@ -217,6 +217,7 @@ "tog-useeditwarning": "M’avertir quand je quitte une page en cours de modification sans avoir sauvegardé", "tog-prefershttps": "Toujours utiliser une connexion sécurisée lorsque je suis connecté", "tog-showrollbackconfirmation": "Afficher une demande de confirmation en cliquant sur un lien d’annulation", + "tog-showrollbackconfirmation-prerelease-warning": "Veuillez prendre note : Cette fonctionnalité n’est pas encore disponible. Si vous définissez cette préférence maintenant, votre choix sera pris en compte [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status quand la fonctionnalité sera livrée].", "underline-always": "Toujours", "underline-never": "Jamais", "underline-default": "Valeur par défaut du thème ou du navigateur", @@ -2432,6 +2433,9 @@ "deleting-backlinks-warning": "Attention : [[Special:WhatLinksHere/{{FULLPAGENAME}}|D’autres pages]] lient vers ou incluent la page que vous allez supprimer.", "deleting-subpages-warning": "Attention : la page que vous essayez de supprimer possède [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|une sous-page|$1 sous-pages|51=plus de 50 sous-pages}}]].", "rollback": "Révoquer les modifications", + "rollback-confirmation-confirm": "Veuillez confirmer :", + "rollback-confirmation-yes": "Révoquer", + "rollback-confirmation-no": "Annuler", "rollbacklink": "révoquer", "rollbacklinkcount": "révoquer $1 {{PLURAL:$1|modification|modifications}}", "rollbacklinkcount-morethan": "révoquer plus de $1 {{PLURAL:$1|modification|modifications}}", @@ -2740,7 +2744,7 @@ "ip_range_exceeded": "L'intervalle des adresses IP est plus grand l'intervalle maximum. Intervalle autorisé : /$1 .", "ip_range_toolow": "Les intervalles d'adresses IP ne sont effectivement pas autorisés.", "proxyblocker": "Bloqueur de mandataires", - "proxyblockreason": "Votre adresse IP a été bloquée car il s’agit d’un mandataire ouvert.\nVeuillez contacter votre fournisseur d’accès Internet ou votre soutien technique et l’informer de ce sérieux problème de sécurité.", + "proxyblockreason": "Votre adresse IP a été bloquée car il s’agit d’un mandataire ouvert.\nVeuillez contacter votre fournisseur d’accès à Internet ou votre service d’assistance technique et l’informer de ce sérieux problème de sécurité.", "sorbsreason": "Votre adresse IP est listée comme mandataire ouvert dans le DNSBL utilisé par {{SITENAME}}.", "sorbs_create_account_reason": "Votre adresse IP est listée comme mandataire ouvert dans le DNSBL utilisé par {{SITENAME}}.\nVous ne pouvez pas créer un compte.", "softblockrangesreason": "Les contributions anonymes ne sont pas autorisées à partir de votre adresse IP ($1). Veuillez vous connecter.", @@ -3233,6 +3237,7 @@ "confirm-unwatch-top": "Supprimer cette page de votre liste de suivi ?", "confirm-rollback-button": "OK", "confirm-rollback-top": "Révoquer les modifications de cette page ?", + "confirm-rollback-bottom": "Cette action annulera immédiatement les modifications sélectionnées sur cette page.", "confirm-mcrrestore-title": "Restaurer une version", "confirm-mcrundo-title": "Annuler une modification", "mcrundofailed": "L’annulation a échoué", diff --git a/languages/i18n/fy.json b/languages/i18n/fy.json index 4211a20bb5..b13c2823a9 100644 --- a/languages/i18n/fy.json +++ b/languages/i18n/fy.json @@ -58,7 +58,7 @@ "tog-ccmeonemails": "Stjoer my in kopy fan e-mails dy't ik nei oare meidoggers stjoer", "tog-diffonly": "Side-ynhâld dy't feroare wurdt net sjen litte", "tog-showhiddencats": "Ferburgen kategoryen werjaan", - "tog-norollbackdiff": "Feroarings weilitte nei tebekdraaien", + "tog-norollbackdiff": "Gjin ferskillen sjen litte nei it útfieren fan weromdraaien", "underline-always": "Altyd", "underline-never": "Nea", "underline-default": "Webblêder-standert", @@ -320,7 +320,7 @@ "protectedinterface": "Dizze side jout systeemteksten fan 'e software en is befeilige tsjin misbrûk. Asto oersettingen foar alle wiki's tafoegje of bewurkje wolst, kinsto [https://translatewiki.net/ translatewiki.net] brûke.", "editinginterface": "Warskôging: Jo bewurkje in side dy't brûkt wurdt foar systeemteksten foar de software.\nBewurkings op dizze side beynfloedzje de meidoggersynterface fan elkenien.", "cascadeprotected": "Dizze side is skoattele tsjin wizigjen, om't der in ûnderdiel útmakket fan de neikommende {{PLURAL:$1|side|siden}}, dy't skoattele {{PLURAL:$1|is|binne}} mei de \"ûnderlizzende siden\" opsje ynskeakele: $2", - "namespaceprotected": "Jo hawwe gjin rjochten om siden yn'e nammerûmte '''$1''' te bewurkjen.", + "namespaceprotected": "Jo hawwe gjin rjochten om siden yn'e nammeromte '''$1''' te bewurkjen.", "ns-specialprotected": "Siden yn'e nammerûmte {{ns:special}} kinne net bewurke wurde.", "titleprotected": "It oanmeitsjen fan dizze side is befeilige troch [[User:$1|$1]].\nDe oanfierde reden is $2.", "exception-nologin": "Net oanmeld", @@ -493,7 +493,7 @@ "newarticle": "(Nij)", "newarticletext": "Jo hawwe in keppeling folge nei in side dêr't noch gjin tekst op stiet.\nOm sels tekst te meistjsen kinne jo dy gewoan yntype in dit bewurkingsfjild\n([$1 Mear ynformaasje oer bewurkjen].)\nOars kinne jo tebek mei de tebek-knop fan jo blêder.", "anontalkpagetext": "----\nDit is de oerlisside fan in ûnbekende meidogger; in meidogger dy't him/har net oanmeld hat.\nOm't der gjin namme bekend is, wurdt it ynternet-adres brûkt om oan te jaan om wa't it giet.\nMar faak is it sa dat sa'n adres net altyd troch deselde persoan brûkt wurdt.\nAs jo it idee hawwe dat jo as ûnbekende meidogger opmerkings foar in oar krije, dan kinne jo jo [[Special:CreateAccount|registrearje]], of jo [[Special:UserLogin|oanmelde]]. Fan in oanmelde meidogger is it ynternet-adres net sichtber, en as oanmelde meidogger krije jo allinnich opmerkings dy't foar josels bedoeld binne.", - "noarticletext": "Der stjit noch gjin tekst op dizze side. Jo kinne\n[[Special:Search/{{PAGENAME}}|hjirboppe nei dy tekst sykje]], of [{{fullurl:{{FULLPAGENAME}}|action=edit}} de side skriuwe].", + "noarticletext": "Der stiet noch gjin tekst op dizze side.\nJo kinne [[Special:Search/{{PAGENAME}}|nei dizze sidenamme sykje]] yn oare siden,\n[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de besibbe lochs trochsykje]\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} dizze side oanmeitsje].", "userpage-userdoesnotexist": "Jo bewurkje in meidoggerside fan in meidogger dy't net bestiet (meidogger \"$1\").\nKontrolearje oft jo dizze side wol oanmeitsje/bewurkje wolle.", "userpage-userdoesnotexist-view": "It meidochakkount \"$1\" bestiet net.", "clearyourcache": "Opmerking: Nei it fêstlizzen kin it nedich wêze de oerslach fan dyn blêder te leegjen foardat de wizigings te sjen binne.\n* Firefox / Safari: Hâld Shift yntreaun wylst jo op Dizze side fernije klikke, of typ Ctrl-F5 of Ctrl-R (⌘-R op in Mac)\n* Google Chrome: Typ CTRL-Shift-R (⌘-Shift-R op in Mac)\n* Internet Explorer: Hâld Ctrl yntreaun wylst jo Vernieuwen'' klikke of typ Ctrl-F5\n* Opera: Leegje jo cache yn Extra → Voorkeuren", @@ -505,6 +505,7 @@ "updated": "(Bewurke)", "note": "Opmerking:", "previewnote": "Tink der om dat dit allinnich in foarfertoaning is!\nJo feroarings binne noch net fêstlein!", + "continue-editing": "Nei it bewurkingsfjild gean", "previewconflict": "Dizze side belanget allinich it earste bewurkingsfjild oan.", "session_fail_preview": "Jo bewurking is net ferwurke, om't de sessygegevens ferlern gien binne.\nBesykje it nochris. As it dan noch net slagget, [[Special:UserLogout|meld jo dan ôf]] en wer oan.", "session_fail_preview_html": "Jo bewurking is net ferwurke, om't sesjegegevens ferlern gien binne.\n\nOm't yn {{SITENAME}} rûge HTML ynskeakele is, is in foarfertoaning net mûglik as beskerming tsjin oanfallen mei JavaScript.\n\nAs dit in legitime bewurking is, besykje it dan fannijs.\nAs it dan noch net slagget, [[Special:UserLogout|meld jo dan ôf]] en wer oan.", @@ -681,7 +682,7 @@ "searchprofile-articles-tooltip": "Sykje yn $1", "searchprofile-images-tooltip": "Sykje om bestannen", "searchprofile-everything-tooltip": "Alle ynhâld trochsykje (ynklusyf oerlissiden)", - "searchprofile-advanced-tooltip": "Sykje yn oanjûne nammerûmten", + "searchprofile-advanced-tooltip": "Sykje yn oanjûne nammeromten", "search-result-size": "$1 ({{PLURAL:$2|1 wurd|$2 wurden}})", "search-redirect": "(trochferwizing $1)", "search-section": "(seksje $1)", @@ -870,8 +871,8 @@ "right-editusercss": "De CSS-bestannen fan oare meidoggers bewurkje", "right-edituserjson": "De JSON-bestannen fan oare meidoggers bewurkje", "right-edituserjs": "De JS-bestannen fan oare meidoggers bewurkje", - "right-rollback": "Gau de bewurkings omdraaie fan 'e lêste meidogger dy't in beskate side bewurke", - "right-markbotedits": "Tebekdraaide bewurkings markearje as botbewurkings", + "right-rollback": "Gau de bewurkings weromdraaie fan 'e lêste meidogger dy't in beskate side bewurke", + "right-markbotedits": "Weromdraaide bewurkings markearje as botbewurkings", "right-noratelimit": "Hat gjin tiidsôfhinklike beheinings", "right-import": "Siden út oare wiki's ymportearje", "right-importupload": "Siden ymportearje út in opladen bestân", @@ -914,7 +915,7 @@ "action-suppressionlog": "dit beskerme logboek besjen", "action-block": "dizze meidogger in bewurkingsblokkade oplizze", "action-protect": "it befeiligingsnivo fan dizze side oanpasse", - "action-rollback": "gau de bewurkings omdraaien fan 'e lêste meidogger dy't in beskate side bewurke", + "action-rollback": "gau de bewurkings weromdraaien fan 'e lêste meidogger dy't in beskate side bewurke", "action-import": "dizze side fan in oare wiki ymportearje", "action-importupload": "siden ymportearjen út in opladen bestân", "action-patrol": "bewurkings fan oaren as kontrolearre beskôgje", @@ -1078,7 +1079,7 @@ "listfiles-latestversion-no": "Nee", "file-anchor-link": "Bestân", "filehist": "Bestânsskiednis", - "filehist-help": "Klik op in tiid om de ferzje fan it bestân op dat stuit te sjen.", + "filehist-help": "Klik op in datum/tiid, en besjoch it bestân sa't it op dat stuit wie.", "filehist-deleteall": "wiskje alles", "filehist-deleteone": "fuortsmite", "filehist-revert": "werom sette", @@ -1138,7 +1139,7 @@ "randompage-nopages": "Der binne gjin siden yn'e nammeromte \"$1\".", "randomincategory-category": "Kategory:", "randomredirect": "Samar in trochferwizing", - "randomredirect-nopages": "Der binne gjin trochferwizings yn'e nammerûmte \"$1\".", + "randomredirect-nopages": "Der binne gjin trochferwizings yn'e nammeromte \"$1\".", "statistics": "Statistyk", "statistics-header-pages": "Sidestatistiken", "statistics-header-edits": "Bewurkingsstatistiken", @@ -1250,7 +1251,7 @@ "allpagessubmit": "Los!", "allpagesprefix": "Siden sjen litte dy't begjinne mei:", "allpagesbadtitle": "De opjûne sidenamme is ûnjildich of hat in yntertaal- of ynterwikifoarheaksel.\nMûglik befettet de namme karakters dy't net brûkt wurde meie yn sidenammen.", - "allpages-bad-ns": "{{SITENAME}} hat gjin nammerûmte \"$1\".", + "allpages-bad-ns": "{{SITENAME}} hat gjin nammeromte \"$1\".", "categories": "Kategoryen", "categoriespagetext": "De folgjende {{PLURAL:$1|kategory bestiet|kategoryen bestean}} op 'e wiki, en {{PLURAL:$1|is|binne}} al of net brûkt.\nSjoch ek [[Special:WantedCategories|Net-besteande kategoryen mei ferwizings]].", "categoriesfrom": "Kategoryen werjaan fan .. ôf:", @@ -1344,16 +1345,16 @@ "deleteotherreason": "Oare/eventuele reden:", "deletereasonotherlist": "Oare reden", "deletereason-dropdown": "*Faak-brûkte redenen\n** Frege troch de skriuwer\n** Skeining fan auteursrjocht\n** Fandalisme", - "rollback": "Bewurkings omdraaie", - "rollbacklink": "omdraaie", - "rollbacklinkcount": "$1 {{PLURAL:$1|bewurking|bewurkings}} omdraaie", - "rollbacklinkcount-morethan": "mear as $1 {{PLURAL:$1|bewurking|bewurkings}} omdraaie", - "rollbackfailed": "Omdraaie net slagge", + "rollback": "Wizigings weromdraaie", + "rollbacklink": "weromdraaie", + "rollbacklinkcount": "$1 {{PLURAL:$1|bewurking|bewurkings}} weromdraaie", + "rollbacklinkcount-morethan": "mear as $1 {{PLURAL:$1|bewurking|bewurkings}} weromdraaie", + "rollbackfailed": "Weromdraaien fan wizigings net slagge.", "cantrollback": "Dizze feroaring kin net werom setten wurde, om't der mar ien skriuwer is.", - "alreadyrolled": "Kin de feroaring fan [[:$1]] troch [[User:$2|$2]] ([[User talk:$2|oerlis]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) net werom sette;\nin oar hat de feroaring werom set, of oars wat oan de side feroare.\n\nDe lêste feroaring wie fan [[User:$3|$3]] ([[User talk:$3|oerlis]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).", + "alreadyrolled": "Kin de wiziging fan [[:$1]] troch [[User:$2|$2]] ([[User talk:$2|oerlis]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) net weromdraaie;\nin oar hat de wiziging weromdraaid, of oars wat oan de side feroare.\n\nDe lêste wiziging wie fan [[User:$3|$3]] ([[User talk:$3|oerlis]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).", "editcomment": "De gearfetting wie: $1.", "revertpage": "Bewurkings fan [[Special:Contributions/$2|$2]] ([[User talk:$2|oerlis]]) weromset ta de lêste ferzje fan [[User:$1|$1]]", - "rollback-success": "Bewurkings fan {{GENDER:$3|$1}} omdraaid;\nde lêste ferzje fan {{GENDER:$4|$2}} weromset.", + "rollback-success": "Wizigings fan {{GENDER:$3|$1}} weromdraaid;\nde lêste ferzje fan {{GENDER:$4|$2}} weromset.", "protectlogpage": "Skoattelloch", "protectlogtext": "Hjirûnder wurdt it skoateljen en frijjaan fan siden oanjûn.\nSjoch [[Special:ProtectedPages|Skoattele side]] foar mear ynformaasje.", "protectedarticle": "\"[[$1]]\" skoattele", @@ -1512,6 +1513,7 @@ "movepagebtn": "Side omneame", "pagemovedsub": "Werneamen slagge", "movepage-moved": "\"$1\" hjit no \"$2\"", + "movepage-moved-redirect": "In trochferwizing is oanmakke.", "articleexists": "Der is al in side mei dy namme, of oars is de namme dy't jo oanjûn hawwe net tastien. Besykje it op 'e nij.", "movetalk": "Titel fan oerlisside ek feroarje, as dy der is.", "movepage-page-moved": "De side $1 is werneamd nei $2.", @@ -1521,7 +1523,7 @@ "revertmove": "werom sette", "delete_and_move_text": "== Wiskjen nedich ==\nDe doelside \"[[:$1]]\" is der al.\nMoat dy wiske wurde om plak te meitsjen foar it werneamen?", "delete_and_move_confirm": "Ja, wiskje de side", - "delete_and_move_reason": "Wiske om plak te meitsjen foar in werneamde side", + "delete_and_move_reason": "Wiske en meitsje plak foar it werneamen fan \"[[$1]]\"", "export": "Eksportearje", "exportall": "Alle siden eksportearje", "export-submit": "Eksportearje", @@ -1573,7 +1575,7 @@ "tooltip-pt-login": "Jo wurde fan herten útnûge jo oan te melden, mar it hoecht net.", "tooltip-pt-logout": "Ofmelde", "tooltip-pt-createaccount": "Jo wurde fan herten útnûge in akkount oan te meitsjen en jo oan te melden, mar it hoecht net.", - "tooltip-ca-talk": "Oerlis oer dizze side", + "tooltip-ca-talk": "Oerlis oer de ynhâldlike side", "tooltip-ca-edit": "Dizze side bewurkje", "tooltip-ca-addsection": "In opmerking tafoegje oan de oerlis-side.", "tooltip-ca-viewsource": "Dizze side is befeilige, mar jo kinne de boarne wol besjen.", @@ -1605,7 +1607,7 @@ "tooltip-t-specialpages": "List fan alle bysûndere siden", "tooltip-t-print": "Ofdrukferzje fan dizze side", "tooltip-t-permalink": "Bliuwende keppeling nei dizze ferzje fan 'e side", - "tooltip-ca-nstab-main": "Ynhâldlike side sjen litte", + "tooltip-ca-nstab-main": "De ynhâldlike side sjen litte", "tooltip-ca-nstab-user": "Besjoch de meidoggerside", "tooltip-ca-nstab-special": "Dit is in bysûndere side, en kin net bewurke wurde", "tooltip-ca-nstab-project": "Projektside sjen litte", @@ -1620,7 +1622,7 @@ "tooltip-diff": "Sjen litte hokker feroarings jo yn'e tekst makke hawwe.", "tooltip-compareselectedversions": "Sjoch de ferskillen tusken de twa keazen ferzjes fan dizze side.", "tooltip-watch": "Foegje dizze side ta oan jo folchlist [alt-w]", - "tooltip-rollback": "\"Omdraaie\" keart de bewurking(s) fan 'e lêste bydrager oan dizze side yn ien klik om", + "tooltip-rollback": "\"Weromdraaie\" set dizze side yn ien klik werom nei hoe't er wie foar't de lêste bydrager syn bewurkings trochfierde", "interlanguage-link-title": "$1 – $2", "interlanguage-link-title-nonlang": "$1 – $2", "common.js": "/* Alles wat hjir oan JavaScript delset wurdt, wurdt foar alle meidoggers laden foar eltse side! */", @@ -1765,7 +1767,7 @@ "autosumm-blank": "Alle ynhâld fan de side weismiten", "autosumm-replace": "Side ferfong mei '$1'", "autoredircomment": "Ferwiist troch nei [[$1]]", - "autosumm-new": "Nije Side: $1", + "autosumm-new": "Side makke mei \"$1\"", "size-bytes": "$1 B", "size-kilobytes": "$1 KB", "size-megabytes": "$1 MB", @@ -1851,6 +1853,10 @@ "blankpage": "Side is leech", "intentionallyblankpage": "Dizze side is bewust leech lizzen en wurdt brûkt foar benchmarks, ensfh.", "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Lebel|Lebels}}]]: $2", + "tag-mw-new-redirect": "Nije trochferwizing", + "tag-mw-changed-redirect-target": "Trochferwizingsdoel feroare", + "tag-mw-changed-redirect-target-description": "Bewurkings dy't it einpunt fan in trochferwizing feroarje", + "tag-mw-undo": "Ungedien meitsjen", "tags-source-header": "Boarne", "tags-active-header": "Aktyf?", "tags-actions-header": "Aksjes", @@ -1882,8 +1888,14 @@ "htmlform-yes": "Ja", "htmlform-cloner-create": "Mear tafoegje", "htmlform-cloner-delete": "Fuortsmite", + "logentry-delete-delete": "$1 {{GENDER:$2|hat}} de side $3 wiske", + "logentry-delete-delete_redir": "$1 {{GENDER:$2|hat}} de trochferwizing $3 by it oerskriuwen wiske", "revdelete-restricted": "hat beheinings oplein oan behearders", "revdelete-unrestricted": "hat beheinings foar behearders goedmakke", + "logentry-move-move": "$1 {{GENDER:$2|hat}} de side $3 omneamd ta $4", + "logentry-move-move-noredirect": "$1 {{GENDER:$2|hat}} de side $3 omneamd ta $4 sûnder in trochferwizing efter te litten", + "logentry-move-move_redir": "$1 {{GENDER:$2|hat}} de side $3 omneamd ta $4 fan de trochferwizing", + "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|hat}} de side $3 omneamd ta $4 fan de trochferwizing, sûnder in trochferwizing efter te litten", "logentry-newusers-create": "It meidochakkount $1 is {{GENDER:$2|oanmakke}}", "logentry-newusers-autocreate": "It meidochakkount $1 is automatysk {{GENDER:$2|oanmakke}}", "logentry-upload-upload": "$1 hat $3 {{GENDER:$2|opladen}}", diff --git a/languages/i18n/ga.json b/languages/i18n/ga.json index c0c38322ed..fc566aa109 100644 --- a/languages/i18n/ga.json +++ b/languages/i18n/ga.json @@ -22,7 +22,8 @@ "Macofe", "Tem", "Nmacu", - "ديفيد" + "ديفيد", + "Xqt" ] }, "tog-underline": "Folínte faoi naisc:", @@ -1369,7 +1370,7 @@ "ilsubmit": "Cuardaigh", "bydate": "de réir dáta", "sp-newimages-showfrom": "Taispeáin íomhánna nua as $2, $1", - "days": "{{PLURAL:$1|$1 lá}}", + "days": "$1 lá", "bad_image_list": "Is é seo a leanas an formáid:\n\nNíl ach míreanna liosta amháin (línte ag tosú le *) san áireamh.\nIs riachtanach gur nasc do dhrochchomhad é an chéad nasc ar líne.\nIs eisceachtaí iad na naisc eile ar an líne céanna, .i. leathanaigh gur féidir an comhad a bheith orthu go hinlíne.", "metadata": "Meiteasonraí", "metadata-help": "Tá breis eolais sa comhad seo, curtha, is dócha, as ceamara digiteach ná scanóir a chruthaigh ná a digitigh é.\nMá tá an comhad mionathraithe as an bunleagan, b'fhéidir nach mbeidh ceann de na sonraí fágtha sa comhad atá athruithe.", diff --git a/languages/i18n/gcr.json b/languages/i18n/gcr.json index c033735e56..c6c810e351 100644 --- a/languages/i18n/gcr.json +++ b/languages/i18n/gcr.json @@ -172,15 +172,15 @@ "print": "Enprimé", "view": "Lir", "view-foreign": "Wè asou $1", - "edit": "Modifyé", - "edit-local": "Modifyé dèskripsyon lokal", + "edit": "Chanjé", + "edit-local": "Chanjé dèskripsyon lokal-a", "create": "Kréyé", "create-local": "Ajouté roun dèskripsyon lokal", "delete": "Siprimen", "undelete_short": "Rèstoré {{PLURAL:$1|roun modifikasyon|$1 modifikasyon}}", "viewdeleted_short": "Wè {{PLURAL:$1|roun modifikasyon ki siprimen|$1 modifikasyon ki siprimen}}", "protect": "Protéjé", - "protect_change": "modifyé", + "protect_change": "chanjé", "unprotect": "Chanjé protègsyon-an", "newpage": "Nouvèl paj", "talkpagelinktext": "diskisyon", @@ -248,10 +248,10 @@ "newmessageslinkplural": "{{PLURAL:$1|oun nouvèl mésaj|dé nouvèl mésaj}}", "newmessagesdifflinkplural": "{{PLURAL:$1|dannyé modifikasyon}}", "youhavenewmessagesmulti": "Zòt gen dé nouvèl mésaj asou $1.", - "editsection": "Modifyé", - "editold": "modifyé", + "editsection": "chanjé", + "editold": "chanjé", "viewsourceold": "wè sours-a", - "editlink": "modifyé", + "editlink": "chanjé", "viewsourcelink": "wè sours-a", "editsectionhint": "Modifyé ségsyon-an : $1", "toc": "Baydivan", @@ -392,7 +392,7 @@ "yourpasswordagain": "Konfirmen modipas-a :", "createacct-yourpasswordagain": "Konfirmen modipas-a", "createacct-yourpasswordagain-ph": "Rantré òkò menm modipas-a", - "userlogin-remembermypassword": "Gardé mo sésyon aktiv", + "userlogin-remembermypassword": "Gadé mo sésyon agtiv", "userlogin-signwithsecure": "Itilizé roun konnègsyon sékirizé", "cannotlogin-title": "Enposib di konnègté so kò", "cannotlogin-text": "Konnègsyon-an pa posib", @@ -401,7 +401,7 @@ "cannotcreateaccount-title": "Kréyasyon di kont enposib", "cannotcreateaccount-text": "Kréyasyon-an dirèk di kont itilizatò pa fika agtivé asou sa wiki.", "yourdomainname": "Zòt domenn :", - "password-change-forbidden": "Zòt pa pouvé modifyé mo di pas asou sa wiki.", + "password-change-forbidden": "Zòt pa pouvé chanjé modipas-ya asou sa wiki.", "externaldberror": "Swé roun lérò prodjwi so kò asou baz-a di data d'otantifikasyon, swé zòt pa otorizé à mété à jou zòt kont ègstèrn.", "login": "Konnègsyon", "login-security": "Vérifyé zòt idantité", @@ -435,7 +435,7 @@ "createacct-benefit-body1": "modifikasyon{{PLURAL:$1|}}", "createacct-benefit-body2": "paj{{PLURAL:$1|}}", "createacct-benefit-body3": "{{PLURAL:$1|kontribitò résan}}", - "badretype": "Mo di pas ki zòt sézi pa ka korèsponn.", + "badretype": "Modipas-ya ki zòt sézi pa ka korèsponn.", "usernameinprogress": "Oun kréyasyon di kont pou sa non d'itilizatò ja an kour.\nSouplé, pasyanté.", "userexists": "Non d'itilizatò sézi ja itilizé.\nSouplé, chwézi roun non diféran.", "loginerror": "Lérò di konnègsyon", @@ -453,11 +453,11 @@ "nosuchusershort": "I pa gen kontribitò ké non-an « $1 ».\nSouplé, vérifyé lòrtograf.", "nouserspecified": "Zòt divèt sézi roun non d'itilizatò.", "login-userblocked": "{{GENDER:$1|Sa itilizatò}} bloké. Konnègsyon-an pa otorizé.", - "wrongpassword": "Non d'itilizatò oben mo di pas enkorèk.\nSouplé, éséyé òkò.", + "wrongpassword": "Non-an di itilizatò oben modipas enkorèk.\nSouplé, éséyé òkò.", "wrongpasswordempty": "Zòt pa rantré pyès modipas.\nSouplé, éséyé òkò.", "passwordtooshort": "Zòt mo di pas divèt kontni omwen $1 karaktèr{{PLURAL:$1|}}.", - "passwordtoolong": "Mo di pas pa pouvé dépasé $1 karaktèr{{PLURAL:$1|}}.", - "passwordtoopopular": "Mo di pas ki tròp kouran pa pouvé fika itilizé. Souplé, chwézi roun mo di pas pli difisil à douviné.", + "passwordtoolong": "Modipas-ya pa pouvé dépasé $1 karagtèr{{PLURAL:$1|}}.", + "passwordtoopopular": "Modipas ki tròp kouran pa pouvé fika itilizé. Souplé, chwézi roun modipas ki pi difisil pou sonjé.", "password-name-match": "Zòt mo di pas divèt fika diféran di zòt non d'itilizatò.", "password-login-forbidden": "Litilizasyon-an di sa non d'itilizatò oben di sa modipas sa entèrdi.", "mailmypassword": "Réynisyalizé modipas-a", @@ -504,14 +504,14 @@ "newpassword": "Nouvèl modipas :", "retypenew": "Konfirmen modipas nòv-a :", "resetpass_submit": "Chanjé modipas-a é konnègté so kò.", - "changepassword-success": "Zòt modipas modifyé !", + "changepassword-success": "Zòt modipas chanjé !", "changepassword-throttled": "Zòt fè tròp di tantativ di konnègsyon résaman. \nSouplé, antann $1 anvan di réyéséyé.", "botpasswords": "Modipas di robo", "botpasswords-summary": "Modipas-ya di robo ka pèrmèt di agsédé à roun kont itilizatò vya API-a san itilizé idantifyan-yan di konnègsyon prensipal. Drwè itilizatò-ya ki disponnib lò to konnègté ké roun modipas robo pouvé fika rédjwi.\n\nSi zòt pa ka wè poukisa zòt ké lé fè sa, a ki zòt pa benzwen di fè sa. Pésonn divèt janmen doumandé zòt di an jénéré roun é di bay li.", "botpasswords-disabled": "Modipas-ya di robo dézagtivé.", "botpasswords-no-central-id": "Pou itilizé modipas-ya di robo, zòt divèt fika konnègté à roun kont ki santralizé.", "botpasswords-existing": "Modipas di robo ki ka ègzisté", - "botpasswords-createnew": "Kréyé roun mo di pas nòv di robo", + "botpasswords-createnew": "Kréyé roun nouvèl modipas di robo", "botpasswords-editexisting": "Modifyé roun modipas di robo ki ka ègzisté", "botpasswords-label-needsreset": "(Modipas-a divèt fika réynisyalizé)", "botpasswords-label-appid": "Non di robo :", @@ -535,11 +535,11 @@ "botpasswords-newpassword": "Nouvèl modipas-a pou konnègté so kò à$1 sa $2. Souplé, anréjistré li pou fè référans asou li iltèryòrman.
    (Pou ansyen robo-ya ki ka nésésité ki non-an ki fourni pou konnègsyon-an ka fika menm-an ki non-an di itilizatò évantchwèl, zòt pouvé osi itilizé $3 kou non di itilizatò é $4 kou modipas).", "botpasswords-no-provider": "BotPasswordsSessionProvider pa disponnib.", "botpasswords-restriction-failed": "Rèstrigsyon-yan di modipas di robo ka anpéché sa konnègsyon.", - "botpasswords-invalid-name": "Non-an d'itilizatò spésifyé pa ka kontni di séparatò di mo di pas di robo (« $1 »).", + "botpasswords-invalid-name": "Non-an d'itilizatò ki èspésifyé pa ka kontni di séparatò di modipas di robo (« $1 »).", "botpasswords-not-exist": "{{GENDER:$1|Itilizatò|Itilizatris}}-a « $1 » pa gen di mo di pas di robo nonmen « $2 ».", "botpasswords-needs-reset": "Modipas-a di robo di non « $2 » di itilizatò-a « $1 » divèt fika réynisyalizé.", "resetpass_forbidden": "Modipas-ya pa pouvé fika chanjé.", - "resetpass_forbidden-reason": "Mo di pas pa pouvé fika modifyé : $1", + "resetpass_forbidden-reason": "Modipas-ya pa pouvé fika chanjé : $1", "resetpass-no-info": "Zòt divèt fika konnègté pou agsédé dirèkman à sa paj.", "resetpass-submit-loggedin": "Chanjé di modipas", "resetpass-submit-cancel": "Annilé", @@ -550,7 +550,7 @@ "resetpass-expired-soft": "Zòt modipas èspiré, é divèt fika modifyé. Souplé, chwézi roun nouvèl atchwèlman oben kliké asou « {{int:authprovider-resetpass-skip-label}} » pou fè li plita.", "resetpass-validity-soft": "Zòt modipas pa valid : $1\n\nSouplé, chwézi roun nouvèl modipas atchwèlman, oben kliké asou « {{int:authprovider-resetpass-skip-label}} » pou modifyé li plita.", "passwordreset": "Réynisyalizasyon di modipas", - "passwordreset-text-one": "Ranplisé sa fòrmilèr pou zòt mo di pas.", + "passwordreset-text-one": "Ranpli sa fòrmilèr pou réynisyalizé zòt modipas.", "passwordreset-emaildisabled": "Fongsyonnalité-ya di kourilèt fika dézagtivé asou sa wiki.", "passwordreset-username": "Non di itilizatò :", "passwordreset-domain": "Domenn :", @@ -839,7 +839,7 @@ "protectedarticle": "protéjé « [[$1]] »", "modifiedarticleprotection": "modifyé nivo-a di protègsyon di « [[$1]] »", "protect-default": "Otorizé tout itilizatò-ya", - "restriction-edit": "Modifyé", + "restriction-edit": "Chanjé", "restriction-move": "Rounonmen", "namespace": "Lèspas di non", "invert": "Envèrsé sélègsyon-an", @@ -981,7 +981,7 @@ "pageinfo-magic-words": "{{PLURAL:$1|Mo majik}} ($1)", "pageinfo-hidden-categories": "{{PLURAL:$1|Katégori kaché|}} ($1)", "pageinfo-templates": "{{PLURAL:$1|Modèl enkli}} ($1)", - "pageinfo-toolboxlink": "Lenfòrmasyon asou paj-a", + "pageinfo-toolboxlink": "Lenfòrmasyon asou paj", "pageinfo-contentpage": "Konté kou paj di kontni", "pageinfo-contentpage-yes": "Enren", "patrol-log-page": "Journal dé roulèktir", diff --git a/languages/i18n/he.json b/languages/i18n/he.json index 6f66416411..1716310255 100644 --- a/languages/i18n/he.json +++ b/languages/i18n/he.json @@ -86,6 +86,7 @@ "tog-useeditwarning": "הצגת אזהרה בעת עזיבת דף עריכה עם שינויים שטרם נשמרו", "tog-prefershttps": "תמיד להשתמש בתקשורת מאובטחת לאחר הכניסה לחשבון", "tog-showrollbackconfirmation": "הצגת הודעת אישור לאחר לחיצה על קישור \"שחזור\"", + "tog-showrollbackconfirmation-prerelease-warning": "לתשומת לבך: תכונה זו עדיין אינה זמינה. אם {{GENDER:|תפעיל|תפעילי}} את ההעדפה הזאת עכשיו, בחירתך תישמר ותיכנס לתוקף [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status כשהתכונה תהפוך לזמינה].", "underline-always": "תמיד", "underline-never": "לעולם לא", "underline-default": "ברירת המחדל של העיצוב או של הדפדפן", @@ -2284,6 +2285,9 @@ "deleting-backlinks-warning": "אזהרה: [[Special:WhatLinksHere/{{FULLPAGENAME}}|דפים אחרים]] מקשרים לדף זה (שעומד להימחק) או מכלילים אותו.", "deleting-subpages-warning": "אזהרה: לדף שעומד להימחק יש [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|דף משנה|$1 דפי משנה|51=יותר מ־50 דפי משנה}}]].", "rollback": "שחזור עריכות", + "rollback-confirmation-confirm": "לאישורך:", + "rollback-confirmation-yes": "שחזור", + "rollback-confirmation-no": "ביטול", "rollbacklink": "שחזור", "rollbacklinkcount": "שחזור {{PLURAL:$1|עריכה אחת|$1 עריכות}}", "rollbacklinkcount-morethan": "שחזור יותר מ{{PLURAL:$1|עריכה אחת|־$1 עריכות}}", @@ -3072,6 +3076,7 @@ "confirm-unwatch-top": "להסיר את הדף הזה מרשימת המעקב שלך?", "confirm-rollback-button": "אישור", "confirm-rollback-top": "לשחזר את העריכות בדף זה?", + "confirm-rollback-bottom": "פעולה זו תשחזר באופן מיידי את השינויים שבחרת בדף זה.", "confirm-mcrrestore-title": "שחזור גרסה", "confirm-mcrundo-title": "ביטול שינוי", "mcrundofailed": "הביטול נכשל", diff --git a/languages/i18n/hu.json b/languages/i18n/hu.json index 929235c2cd..41135bc3c2 100644 --- a/languages/i18n/hu.json +++ b/languages/i18n/hu.json @@ -98,6 +98,7 @@ "tog-norollbackdiff": "Ne jelenjenek meg az eltérések visszaállítás után", "tog-useeditwarning": "Figyelmeztessen, ha szerkesztéskor a módosítások mentése nélkül akarom elhagyni a lapot", "tog-prefershttps": "Mindig biztonságos kapcsolatot használjon, amikor be vagyok jelentkezve", + "tog-showrollbackconfirmation": "Megerősítés kérése, amikor a visszaállítás linkre kattintasz", "underline-always": "mindig", "underline-never": "soha", "underline-default": "Felület és böngésző alapértelmezése szerint", @@ -2286,6 +2287,9 @@ "deleting-backlinks-warning": "Figyelem: [[Special:WhatLinksHere/{{FULLPAGENAME}}|Más lapok]] hivatkoznak a törlendő oldalra (vagy beillesztik azt).", "deleting-subpages-warning": "Figyelem: A törlésre jelölt lapnak [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|van egy allapja|$1 allapja van|51=több mint 50 allapja van}}]].", "rollback": "Szerkesztések visszaállítása", + "rollback-confirmation-confirm": "Kérlek erősítsd meg:", + "rollback-confirmation-yes": "Visszaállítás", + "rollback-confirmation-no": "Mégse", "rollbacklink": "visszaállítás", "rollbacklinkcount": "$1 szerkesztés visszaállítása", "rollbacklinkcount-morethan": "több mint $1 szerkesztés visszaállítása", @@ -2492,6 +2496,8 @@ "ipb-confirm": "Blokk megerősítése", "ipb-sitewide": "Teljes körű", "ipb-partial": "Részleges", + "ipb-sitewide-help": "A wiki összes lapja és minden egyéb közreműködési művelet.", + "ipb-partial-help": "Meghatározott lapok vagy névterek.", "ipb-pages-label": "Lapok", "ipb-namespaces-label": "Névterek", "badipaddress": "Érvénytelen IP-cím", @@ -2580,6 +2586,7 @@ "ipb_expiry_old": "A lejárati idő a múltban van.", "ipb_expiry_temp": "A láthatatlan felhasználóinév-blokkok lehetnek állandóak.", "ipb_hide_invalid": "A felhasználói fiókot nem lehet elrejteni; több mint $1 szerkesztése van.", + "ipb_hide_partial": "Felhasználói nevek elrejtésekor és blokkolásakor a blokknak az egész wikire ki kell terjednie.", "ipb_already_blocked": "\"$1\" már blokkolva", "ipb-needreblock": "$1 már blokkolva van. Meg szeretnéd változtatni a beállításokat?", "ipb-otherblocks-header": "További {{PLURAL:$1|blokk|blokkok}}", @@ -3066,6 +3073,7 @@ "confirm-unwatch-top": "El szeretnéd távolítani a lapot a figyelőlistádról?", "confirm-rollback-button": "OK", "confirm-rollback-top": "Visszavonod a változtatásokat?", + "confirm-rollback-bottom": "Ez a művelet azonnal visszaállítja a lap kiválasztott változtatásait.", "confirm-mcrrestore-title": "Egy változat visszaállítása", "confirm-mcrundo-title": "Egy változtatás visszavonva", "mcrundofailed": "A visszavonás nem sikerült", @@ -3771,5 +3779,7 @@ "passwordpolicies-policy-maximalpasswordlength": "A jelszó legfeljebb $1 karakter hosszú lehet", "passwordpolicies-policy-passwordcannotbepopular": "A jelszó nem {{PLURAL:$1|lehet a gyakran használt jelszó|szerepelhet a(z) $1 leggyakrabban használt jelszó listáján}}", "passwordpolicies-policy-passwordnotinlargeblacklist": "A jelszó nem szerepelhet a 100 000 leggyakrabban használt jelszó listáján .", + "passwordpolicies-policyflag-forcechange": "lecserélés követelése bejelentkezéskor", + "passwordpolicies-policyflag-suggestchangeonlogin": "lecserélés ajánlása bejelentkezéskor", "unprotected-js": "Biztonsági okokból JavaScript nem tölthető be védtelen lapokról. Kérlek egyedül a MediaWiki névtérben készíts JavaScriptet, vagy szerkesztői allapként." } diff --git a/languages/i18n/ia.json b/languages/i18n/ia.json index f2739bebdc..3a83cc3b7c 100644 --- a/languages/i18n/ia.json +++ b/languages/i18n/ia.json @@ -64,6 +64,7 @@ "tog-useeditwarning": "Advertir me quando io quita un pagina de modification sin publicar le cambiamentos", "tog-prefershttps": "Sempre usar un connexion secur durante session aperte", "tog-showrollbackconfirmation": "Monstrar un demanda de confirmation al cliccar sur un ligamine de revocation", + "tog-showrollbackconfirmation-prerelease-warning": "Nota ben: Iste function non es ancora disponibile. Si tu defini ora iste preferentia, tu selection essera rememorate al [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status quando le function es preste].", "underline-always": "Sempre", "underline-never": "Nunquam", "underline-default": "Como definite per tu navigator o apparentia", @@ -2257,6 +2258,9 @@ "deleting-backlinks-warning": "Attention: Il ha [[Special:WhatLinksHere/{{FULLPAGENAME}}|altere paginas]] que liga a o transclude le pagina que tu es sur le puncto de deler.", "deleting-subpages-warning": "Attention: Le pagina que tu es sur le puncto de deler ha [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|un subpagina|$1 subpaginas|51=plus de 50 subpaginas}}]].", "rollback": "Revocar modificationes", + "rollback-confirmation-confirm": "Per favor confirma:", + "rollback-confirmation-yes": "Revocar", + "rollback-confirmation-no": "Cancellar", "rollbacklink": "revocar", "rollbacklinkcount": "revocar $1 {{PLURAL:$1|modification|modificationes}}", "rollbacklinkcount-morethan": "revocar plus de $1 {{PLURAL:$1|modification|modificationes}}", @@ -3031,6 +3035,7 @@ "confirm-unwatch-top": "Remover iste pagina de tu observatorio?", "confirm-rollback-button": "OK", "confirm-rollback-top": "Reverter le modificationes a iste pagina?", + "confirm-rollback-bottom": "Iste action revocara instantaneemente le modificationes seligite de iste pagina.", "confirm-mcrrestore-title": "Restaurar version", "confirm-mcrundo-title": "Disfacer un modification", "mcrundofailed": "Disfaction fallite", diff --git a/languages/i18n/io.json b/languages/i18n/io.json index edeeb8a9a3..d7eb2bb4f9 100644 --- a/languages/i18n/io.json +++ b/languages/i18n/io.json @@ -173,7 +173,7 @@ "help": "Helpo", "help-mediawiki": "Helpo pri MediaWiki", "search": "Sercho", - "search-ignored-headings": " #
    \n# Tituli qui ignoresos per la sistemo di serchado.\n# Modifiki en ca parto efikeskos balde pos la titulo di la pagino adicionesos a l'indexo.\n# Tu povas acelerar la riindexigo di la pagino facante nihila editado.\n# La sintaxo esas quale infre:\n#   * Omna texti qui finas kun la signo \"#\" fine de la lineo, esas komentaro.\n#   * Omna lineo ne blanka - to esas: skriptata -, esas l'exakta titulo por ignorar la diferi inter mayuskula e minuskula literi, ed altra.\nReferi\nExtera ligili\nVidez anke\n #
    ", + "search-ignored-headings": " #
    \n# Tituli qui ignoresos dal sistemo di serchado.\n# Modifiki en ca parto efikeskos balde pos la titulo di la pagino adicionesos a l'indexo.\n# Tu povas acelerar la riindexigo di la pagino facante nihila editado.\n# La sintaxo esas quale infre:\n#   * Omna texti qui finas kun la signo \"#\" fine de la lineo, esas komentaro.\n#   * Omna lineo ne blanka - to esas: skriptata -, esas l'exakta titulo por ignorar la diferi inter mayuskula e minuskula literi, ed altra.\nReferi\nExtera ligili\nVidez anke\n #
    ", "searchbutton": "Serchez", "go": "Irar", "searcharticle": "Irez", diff --git a/languages/i18n/it.json b/languages/i18n/it.json index 6dedc1e289..5d810af808 100644 --- a/languages/i18n/it.json +++ b/languages/i18n/it.json @@ -590,7 +590,7 @@ "invalidemailaddress": "L'indirizzo e-mail indicato ha un formato non valido. Inserire un indirizzo valido o svuotare la casella.", "cannotchangeemail": "Gli indirizzi e-mail non possono essere modificati in questo wiki.", "emaildisabled": "Questo sito non può inviare messaggi di posta elettronica.", - "accountcreated": "Accesso creato", + "accountcreated": "Utenza creata", "accountcreatedtext": "È stata creata un'utenza per [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|msg]]).", "createaccount-title": "Creazione di un accesso a {{SITENAME}}", "createaccount-text": "Qualcuno ha creato un accesso a {{SITENAME}} ($4) a nome di $2, associato a questo indirizzo di posta elettronica. La password per l'utente \"$2\" è impostata a \"$3\".\nÈ opportuno eseguire un accesso quanto prima e cambiare la password immediatamente.\n\nSe l'accesso è stato creato per errore, si può ignorare questo messaggio.", @@ -2325,6 +2325,8 @@ "deleting-backlinks-warning": "Attenzione: [[Special:WhatLinksHere/{{FULLPAGENAME}}|altre pagine]] contengono collegamenti o inclusioni alla pagina che stai per cancellare.", "deleting-subpages-warning": "Attenzione: la pagina che stai per cancellare ha [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|una sotto-pagina|$1 sotto-pagine|51=più di 50 sotto-pagine}}]].", "rollback": "Annulla le modifiche", + "rollback-confirmation-yes": "Rollback", + "rollback-confirmation-no": "Annulla", "rollbacklink": "rollback", "rollbacklinkcount": "rollback di {{PLURAL:$1|una modifica|$1 modifiche}}", "rollbacklinkcount-morethan": "rollback di più di {{PLURAL:$1|una modifica|$1 modifiche}}", diff --git a/languages/i18n/ja.json b/languages/i18n/ja.json index f1ec639783..7f170ada06 100644 --- a/languages/i18n/ja.json +++ b/languages/i18n/ja.json @@ -608,7 +608,7 @@ "botpasswords-existing": "既存のボット用パスワード", "botpasswords-createnew": "ボット用パスワードの新規作成", "botpasswords-editexisting": "既存のボットのパスワードを編集", - "botpasswords-label-needsreset": "(パスワードをリセットする必要があります)", + "botpasswords-label-needsreset": "(パスワードのリセットが必要です)", "botpasswords-label-appid": "ボット名:", "botpasswords-label-create": "作成", "botpasswords-label-update": "更新", @@ -633,7 +633,7 @@ "botpasswords-invalid-name": "指定された利用者名には、ボット用パスワードの区切りである「$1」 が含まれていません。", "botpasswords-not-exist": "利用者「$1」はボット「$2」のパスワードを所持していません。", "botpasswords-needs-reset": "{{GENDER:$1|利用者}}「$1」のボット名「$2」のためのパスワードはリセットする必要があります。", - "botpasswords-locked": "アカウントがロックされているため、bot用パスワードではログインできません。", + "botpasswords-locked": "アカウントがロックされているため、ボット用パスワードでログインできません。", "resetpass_forbidden": "パスワードは変更できません", "resetpass_forbidden-reason": "パスワードは変更できません: $1", "resetpass-no-info": "このページに直接アクセスするためにはログインしている必要があります。", diff --git a/languages/i18n/ko.json b/languages/i18n/ko.json index ca465b773a..02ce84de42 100644 --- a/languages/i18n/ko.json +++ b/languages/i18n/ko.json @@ -2315,6 +2315,9 @@ "deleting-backlinks-warning": "경고: 삭제하려는 문서가 [[Special:WhatLinksHere/{{FULLPAGENAME}}|다른 문서]]에 링크되어 있거나 끼워져 있습니다.", "deleting-subpages-warning": "경고: 삭제하려는 문서에 [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|하나의 하위 문서|$1개의 하위 문서|51=50개 이상의 하위 문서}}]]가 있습니다.", "rollback": "편집 되돌리기", + "rollback-confirmation-confirm": "확인해 주십시오:", + "rollback-confirmation-yes": "되돌리기", + "rollback-confirmation-no": "취소", "rollbacklink": "되돌리기", "rollbacklinkcount": "{{PLURAL:$1|편집}} $1회 되돌리기", "rollbacklinkcount-morethan": "{{PLURAL:$1|편집}} $1회 이상 되돌리기", @@ -3078,8 +3081,8 @@ "confirmemail_body_set": "당신일 수도 있는 $1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 지정하였습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을\n활성화하려면 아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.", "confirmemail_invalidated": "이메일 확인이 취소됨", "invalidateemail": "이메일 확인 취소", - "notificationemail_subject_changed": "{{SITENAME}}의 등록된 이메일 주소가 변경되었습니다", - "notificationemail_subject_removed": "{{SITENAME}}의 등록된 이메일 주소가 제거되었습니다", + "notificationemail_subject_changed": "{{SITENAME}}에 등록된 이메일 주소가 변경되었습니다", + "notificationemail_subject_removed": "{{SITENAME}}에 등록된 이메일 주소가 제거되었습니다", "notificationemail_body_changed": "당신일 수도 있는 IP 주소 $1에 속하는 사용자가\n{{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 \"$3\"(으)로 바꾸었습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.", "notificationemail_body_removed": "당신일 수도 있는 IP 주소 $1에 속하는 사용자가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 제거하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.", "scarytranscludedisabled": "[인터위키가 비활성되어 있습니다]", diff --git a/languages/i18n/lb.json b/languages/i18n/lb.json index ec60a63581..a9875851b4 100644 --- a/languages/i18n/lb.json +++ b/languages/i18n/lb.json @@ -2107,6 +2107,8 @@ "deleting-backlinks-warning": "Opgepasst: [[Special:WhatLinksHere/{{FULLPAGENAME}}|Aner Säite]] linken op déi Säit déi Dir am Gaang sidd ze läschen oder déi Säit Déi Dir am Gaang sidd ze läschen ass an aner Säiten agebonn.", "deleting-subpages-warning": "Opgepasst: D'Säit, déi Dir läsche wëllt, huet [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|eng Ënnersäit|$1 Ënnersäiten|51=méi wéi 50 Ënnersäiten}}]].", "rollback": "Ännerungen zrécksetzen", + "rollback-confirmation-yes": "Zrécksetzen", + "rollback-confirmation-no": "Ofbriechen", "rollbacklink": "Zrécksetzen", "rollbacklinkcount": "{{PLURAL:$1|Eng Ännerung|$1 Ännerungen}} zrécksetzen", "rollbacklinkcount-morethan": "méi wéi {{PLURAL:$1|Eng Ännerung|$1 Ännerungen}} zrécksetzen", diff --git a/languages/i18n/mk.json b/languages/i18n/mk.json index 2d3e88b638..20ebf2ab66 100644 --- a/languages/i18n/mk.json +++ b/languages/i18n/mk.json @@ -71,6 +71,7 @@ "tog-useeditwarning": "Предупреди ме кога сакам да напуштам страница за уредување без да ги имам зачувано промените", "tog-prefershttps": "Секогаш најавувај ме преку безбедна врска", "tog-showrollbackconfirmation": "Прикажи потврдница при стискање на врската за отповикување", + "tog-showrollbackconfirmation-prerelease-warning": "Имајте предвид: Оваа можност сè уште не е достапна. Ако ја зададете поставката сега, изборот ќе ви се зачува [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status кога ќе излезе алатката].", "underline-always": "Секогаш", "underline-never": "Никогаш", "underline-default": "Според рувото или прелистувачот", @@ -2276,7 +2277,7 @@ "deleting-backlinks-warning": "Предупредување: До страницата што сакате да ја избришете водат [[Special:WhatLinksHere/{{FULLPAGENAME}}|други страници]] или пак се превметнуваат во неа.", "deleting-subpages-warning": "Предупредување: Страницата што сакате да ја избришете има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|потстраница|$1 потстраници|51=преку 50 потстраници}}]].", "rollback": "Отповикај промени", - "rollback-confirmation-confirm": "Да {{PLURAL:$1|0=ги отповикам овие уредувања|отповикам едно уредување|отповикам $1 уредувања}}?", + "rollback-confirmation-confirm": "Потврдете:", "rollback-confirmation-yes": "Отповикај", "rollback-confirmation-no": "Откажи", "rollbacklink": "отповикај", diff --git a/languages/i18n/ml.json b/languages/i18n/ml.json index 75b69a0f4f..9120560496 100644 --- a/languages/i18n/ml.json +++ b/languages/i18n/ml.json @@ -2181,6 +2181,9 @@ "deleting-backlinks-warning": "മുന്നറിയിപ്പ്: മറ്റു താളുകളിൽ നിന്നും [[Special:WhatLinksHere/{{FULLPAGENAME}}|താളിലേയ്ക്കുള്ള കണ്ണികൾ]] അല്ലെങ്കിൽ ഉൾപ്പെടുത്തിയിട്ടുള്ള താളുകൾ താങ്കൾ മായ്ക്കാൻ പോവുകയാണ്.", "deleting-subpages-warning": "മുന്നറിയിപ്പ്: താങ്കൾ മായ്ക്കാൻ പോകുന്ന താളിന് [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|ഒരു ഉപതാൾ|$1 ഉപതാളുകൾ|51=അമ്പതിലധികം ഉപതാളുകൾ}}]] ഉണ്ട്.", "rollback": "തിരുത്തുകൾ റോൾബാക്ക് ചെയ്യുക", + "rollback-confirmation-confirm": "ദയവായി സ്ഥിരീകരിക്കുക:", + "rollback-confirmation-yes": "റോൾബാക്ക്", + "rollback-confirmation-no": "റദ്ദാക്കുക", "rollbacklink": "റോൾബാക്ക്", "rollbacklinkcount": "{{PLURAL:$1|ഒരു തിരുത്ത്|$1 തിരുത്തുകൾ}} മുൻപ്രാപനം ചെയ്യുക", "rollbacklinkcount-morethan": "{{PLURAL:$1|ഒന്നിലധികം തിരുത്തുകൾ|$1 എണ്ണത്തിലധികം തിരുത്തുകൾ}} മുൻപ്രാപനം ചെയ്യുക", @@ -2962,7 +2965,7 @@ "imgmultipagenext": "അടുത്ത താൾ →", "imgmultigo": "പോകൂ!", "imgmultigoto": "$1 താളിലേക്ക് പോകുക", - "img-lang-default": "(സ്വതേ വേണ്ട ഭാഷ)", + "img-lang-default": "(സ്വതേയുള്ള ഭാഷ)", "img-lang-info": "ഈ ചിത്രം ഈ ഭാഷയിൽ കാണിക്കുക: $1. $2", "img-lang-go": "പോകൂ", "ascending_abbrev": "ആരോഹണം", diff --git a/languages/i18n/ms.json b/languages/i18n/ms.json index 62e2047ec1..61d40cb8f1 100644 --- a/languages/i18n/ms.json +++ b/languages/i18n/ms.json @@ -1205,8 +1205,8 @@ "rightslogtext": "Ini ialah log perubahan terhadap hak pengguna.", "action-read": "membaca laman ini", "action-edit": "menyunting laman ini", - "action-createpage": "mencipta laman", - "action-createtalk": "mencipta laman perbincangan", + "action-createpage": "ciptakan laman ini", + "action-createtalk": "ciptakan laman perbincangan ini", "action-createaccount": "mencipta akaun pengguna ini", "action-history": "melihat sejarah halaman ini", "action-minoredit": "menanda suntingan ini sebagai suntingan kecil", diff --git a/languages/i18n/nan.json b/languages/i18n/nan.json index 89ea2a3d34..ff71467797 100644 --- a/languages/i18n/nan.json +++ b/languages/i18n/nan.json @@ -919,13 +919,15 @@ "cantrollback": "Bô-hoat-tō· kā siu-kái ká-tńg--khì; téng ūi kòng-hiàn-chiá sī chit ia̍h î-it ê chok-chiá.", "alreadyrolled": "Bô-hoat-tō· kā [[User:$2|$2]] ([[User talk:$2|Thó-lūn]]) tùi [[:$1]] ê siu-kái ká-tńg-khì; í-keng ū lâng siu-kái a̍h-sī ká-tńg chit ia̍h. Téng 1 ūi siu-kái-chiá sī [[User:$3|$3]] ([[User talk:$3|Thó-lūn]]).", "editcomment": "Siu-kái phêng-lūn sī: $1.", - "protectedarticle": "pó-hō͘ \"[[$1]]\"", + "protectedarticle": "pó-hō͘ liáu \"[[$1]]\"", "protect-title": "Kái-piàn \"$1\" ê pó-hō͘ chân-kip", "prot_1movedto2": "[[$1]] sóa khì tī [[$2]]", "protect-legend": "Khak-tēng beh pó-hō·", "protectcomment": "Lí-iû:", - "protect-level-autoconfirmed": "Chí ín-chún chū-tōng khak-jīn iōng-chiá", - "protect-level-sysop": "Chí ín-chún koán-lí jîn-oân", + "protect-level-autoconfirmed": "Ta ín-chún chū-tōng khak-jīn iōng-chiá", + "protect-level-sysop": "Ta ín-chún koán-lí jîn-oân", + "protect-expiring": "chì $1 (UTC) kòe-kî", + "protect-expiring-local": "chì $1 kòe-kî", "protect-cascade": "Cascading protection - pó-hō͘ jīm-hô pau-hâm tī chit ia̍h ê ia̍h.", "restriction-edit": "Siu-kái", "restriction-move": "Sóa khì", diff --git a/languages/i18n/nl.json b/languages/i18n/nl.json index befe0f4cf9..9eef5b7c54 100644 --- a/languages/i18n/nl.json +++ b/languages/i18n/nl.json @@ -2335,6 +2335,8 @@ "deleting-backlinks-warning": "Waarschuwing: [[Special:WhatLinksHere/{{FULLPAGENAME}}|andere pagina's]] gebruiken of verwijzen naar de pagina die u wilt verwijderen.", "deleting-subpages-warning": "Waarschuwing:De pagina die u wilt verwijderen heeft [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|een deelpagina|$1 deelpagina's|51=meer dan 50 deelpagina's}}]].", "rollback": "Wijzigingen ongedaan maken", + "rollback-confirmation-yes": "Terugdraaien", + "rollback-confirmation-no": "Annuleren", "rollbacklink": "terugdraaien", "rollbacklinkcount": "{{PLURAL:$1|één bewerking|$1 bewerkingen}} terugdraaien", "rollbacklinkcount-morethan": "Meer dan {{PLURAL:$1|één bewerking|$1 bewerkingen}} terugdraaien", diff --git a/languages/i18n/pl.json b/languages/i18n/pl.json index 1749737322..b8e6e83886 100644 --- a/languages/i18n/pl.json +++ b/languages/i18n/pl.json @@ -147,6 +147,7 @@ "tog-useeditwarning": "Ostrzegaj mnie, gdy opuszczam stronę edycji bez zapisania zmian", "tog-prefershttps": "Zawsze używaj bezpiecznego połączenia po zalogowaniu", "tog-showrollbackconfirmation": "Wyświetl komunikat potwierdzający kliknięcie linku wycofującego edycję", + "tog-showrollbackconfirmation-prerelease-warning": "Zauważ, że ta funkcja nie jest jeszcze dostępna. Jeżeli teraz ustawisz tą preferencję, twój wybór zostanie zapamiętany [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status gdy funkcja zostanie wydana].", "underline-always": "Zawsze", "underline-never": "Nigdy", "underline-default": "według ustawień skórki lub przeglądarki", @@ -2343,6 +2344,9 @@ "deleting-backlinks-warning": "Uwaga: Do strony, którą masz zamiar usunąć, odwołują się [[Special:WhatLinksHere/{{FULLPAGENAME}}|inne strony]].", "deleting-subpages-warning": "Ostrzeżenie: Strona którą chcesz usunąć ma [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|jedną podstronę|$1 podstrony|$1 podstron|51=ponad 50 podstron}}]].", "rollback": "Cofnij edycję", + "rollback-confirmation-confirm": "Potwierdź:", + "rollback-confirmation-yes": "Wycofaj", + "rollback-confirmation-no": "Anuluj", "rollbacklink": "cofnij", "rollbacklinkcount": "cofnij $1 {{PLURAL:$1|edycję|edycje|edycji}}", "rollbacklinkcount-morethan": "cofnij więcej niż $1 {{PLURAL:$1|edycję|edycje|edycji}}", @@ -3129,6 +3133,7 @@ "confirm-unwatch-top": "Usunąć tę stronę z listy obserwowanych?", "confirm-rollback-button": "OK", "confirm-rollback-top": "Wycofać edycje tej strony?", + "confirm-rollback-bottom": "Ta akcja natychmiastowo wycofa wybrane zmiany wykonane na tej stronie.", "confirm-mcrrestore-title": "Odtwórz wersję", "confirm-mcrundo-title": "Cofnij zmianę", "mcrundofailed": "Cofnięcie nie powiodło się", diff --git a/languages/i18n/pt-br.json b/languages/i18n/pt-br.json index 933b98ac25..d264c0dfdd 100644 --- a/languages/i18n/pt-br.json +++ b/languages/i18n/pt-br.json @@ -2384,6 +2384,7 @@ "deleting-backlinks-warning": "'''Cuidado:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|outras páginas]] ligam ou redirecionam para a página que você está prestes a eliminar.", "deleting-subpages-warning": "Aviso: A página que você está prestes a excluir tem [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|uma subpágina|$1 subpáginas|51=mais de 50 subpáginas}}]].", "rollback": "Reverter edições", + "rollback-confirmation-confirm": "Por favor confirme:", "rollback-confirmation-yes": "Reverter", "rollback-confirmation-no": "Cancelar", "rollbacklink": "reverter", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 3438272be3..7b0533a739 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2598,7 +2598,7 @@ "rollback": "{{Identical|Rollback}}", "rollback-confirmation-confirm": "Prompt which asks the user to confirm that they want to really perform the rollback action after clicking on the rollback button.", "rollback-confirmation-yes": "Button text to confirm that a rollback should be executed.", - "rollback-confirmation-no": "Button text to cancel a rollback instead of executing it.", + "rollback-confirmation-no": "Button text to cancel a rollback instead of executing it.\n{{Identical|Cancel}}", "rollbacklink": "{{Doc-actionlink}}\nThis link text appears on the recent changes page to users who have the \"rollback\" right.\nThis message has a tooltip {{msg-mw|tooltip-rollback}}\n{{Identical|Rollback}}", "rollbacklinkcount": "{{doc-actionlink}}\nText of the rollback link showing the number of edits to be rolled back. See also {{msg-mw|rollbacklink}}.\n\nParameters:\n* $1 - the number of edits that will be rolled back. If $1 is over the value of $wgShowRollbackEditCount (default: 10) {{msg-mw|rollbacklinkcount-morethan}} is used.\n\nThe rollback link is displayed with a tooltip {{msg-mw|Tooltip-rollback}}", "rollbacklinkcount-morethan": "{{doc-actionlink}}\nText of the rollback link when a greater number of edits is to be rolled back. See also {{msg-mw|rollbacklink}}.\n\nWhen the number of edits rolled back is smaller than [[mw:Special:MyLanguage/Manual:$wgShowRollbackEditCount|$wgShowRollbackEditCount]], {{msg-mw|rollbacklinkcount}} is used instead.\n\nParameters:\n* $1 - number of edits", diff --git a/languages/i18n/roa-tara.json b/languages/i18n/roa-tara.json index 8f422cfac9..5e37f0e8bb 100644 --- a/languages/i18n/roa-tara.json +++ b/languages/i18n/roa-tara.json @@ -2123,6 +2123,7 @@ "deleteprotected": "Non ge puè scangellà sta pàgene purcé ha state protette.", "deleting-backlinks-warning": "Attenziò: [[Special:WhatLinksHere/{{FULLPAGENAME}}|Otre pàggene]] appondene o vonne 'a pàgene ca tu vue ccù scangìlle.", "rollback": "Annulle le cangiaminde", + "rollback-confirmation-confirm": "Pe piacere conferme:", "rollbacklink": "annulle 'u cangiaminde", "rollbacklinkcount": "annulle $1 {{PLURAL:$1|cangiamende|cangiaminde}}", "rollbacklinkcount-morethan": "annulle cchiù de $1 {{PLURAL:$1|cangiamende|cangiaminde}}", diff --git a/languages/i18n/ru.json b/languages/i18n/ru.json index 7c541ed094..6fcd0a3e59 100644 --- a/languages/i18n/ru.json +++ b/languages/i18n/ru.json @@ -138,7 +138,8 @@ "Cronolio", "Nk88", "Edward Chernenko", - "Romanko Mikhail" + "Romanko Mikhail", + "Diralik" ] }, "tog-underline": "Подчёркивание ссылок:", @@ -469,7 +470,7 @@ "title-invalid-interwiki": "Запрашиваемое название страницы содержит интервики-ссылку, которая не может быть использована в названиях.", "title-invalid-talk-namespace": "Запрашиваемое название страницы ссылается на страницу обсуждения, которая не может существовать.", "title-invalid-characters": "Запрашиваемое название страницы содержит недопустимые символы: «$1».", - "title-invalid-relative": "Заголовок имеет относительный путь. Заголовки страниц с относительным путем (/,../) являются недействительными, так как они часто недоступны, когда обрабатываются браузером пользователя.", + "title-invalid-relative": "Заголовок имеет относительный путь. Заголовки страниц с относительным путём (/,../) являются недействительными, так как они часто недоступны, когда обрабатываются браузером пользователя.", "title-invalid-magic-tilde": "Запрашиваемый заголовок страницы содержит недопустимую последовательность тильды (~~~).", "title-invalid-too-long": "Запрашиваемый заголовок страницы слишком длинен. Он должен быть не более $1 {{PLURAL:$1|1=байта|байт}} в кодировке UTF-8.", "title-invalid-leading-colon": "Запрашиваемое название страницы содержит недопустимое двоеточие в начале.", @@ -503,7 +504,7 @@ "titleprotected": "Создание страницы с таким заголовком было запрещено участником [[User:$1|$1]].\nУказана следующая причина: $2.", "filereadonlyerror": "Не удаётся изменить файл «$1», так как хранилище «$2» находится в режиме «только для чтения».\n\nСистемный администратор, заблокировавший базу, оставил следующее объяснение: «$3».", "invalidtitle": "Недопустимое название", - "invalidtitle-knownnamespace": "Недопустимый заголовок с пространством имен «$2» и текстом «$3»", + "invalidtitle-knownnamespace": "Недопустимый заголовок с пространством имён «$2» и текстом «$3»", "invalidtitle-unknownnamespace": "Недопустимый заголовок с неизвестным номером пространства $1 и текстом «$2»", "exception-nologin": "Вы не представились системе", "exception-nologin-text": "Необходимо представиться, чтобы иметь доступ к этой странице или действию.", @@ -534,7 +535,7 @@ "cannotloginnow-title": "Невозможно войти прямо сейчас", "cannotloginnow-text": "Нельзя войти во время использования $1.", "cannotcreateaccount-title": "Невозможно создать учётные записи", - "cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.", + "cannotcreateaccount-text": "Прямое создание учётных записей не включено в этой вики.", "yourdomainname": "Ваш домен:", "password-change-forbidden": "Вы не можете изменить пароль в этой вики.", "externaldberror": "Произошла ошибка при аутентификации с помощью внешней базы данных или у вас недостаточно прав для внесения изменений в свою внешнюю учётную запись.", @@ -557,7 +558,7 @@ "createacct-email-ph": "Введите свой адрес электронной почты", "createacct-another-email-ph": "Введите адрес электронной почты", "createaccountmail": "Использовать сгенерированный случайным образом временный пароль и выслать его на указанный адрес электронной почты", - "createaccountmail-help": "Может использоваться, чтобы создать учетную запись для другого лица, не узнавая пароль.", + "createaccountmail-help": "Может использоваться, чтобы создать учётную запись для другого лица, не узнавая пароль.", "createacct-realname": "Настоящее имя (необязательно)", "createacct-reason": "Причина", "createacct-reason-ph": "Зачем вы создаёте другую учётную запись", @@ -587,7 +588,7 @@ "nosuchuser": "Участника с именем «$1» не существует.\nИмена участников чувствительны к регистру букв.\nПроверьте правильность написания имени или [[Special:CreateAccount|создайте новую учётную запись]].", "nosuchusershort": "Не существует участника с именем «$1». Проверьте написание имени.", "nouserspecified": "Вы должны указать имя участника.", - "login-userblocked": "Участник заблокирован. Вход в систему запрещен.", + "login-userblocked": "Участник заблокирован. Вход в систему запрещён.", "wrongpassword": "Введены неверные имя участника или пароль.\nПопробуйте ещё раз.", "wrongpasswordempty": "Пожалуйста, введите непустой пароль.", "passwordtooshort": "Пароль должен состоять не менее, чем из $1 {{PLURAL:$1|символа|символов}}.", @@ -975,9 +976,9 @@ "revdelete-selected-text": "{{PLURAL:$1|Выбранная версия|Выбранные версии}} [[:$2]]:", "revdelete-selected-file": "{{PLURAL:$1|Выбранная версия файла|Выбранные версии файла}} [[:$2]]:", "logdelete-selected": "{{PLURAL:$1|1=Выбранная запись|Выбранные записи}} журнала:", - "revdelete-text-text": "Удалённые версии будут по-прежнему видны в истории страницы, но определенные части их содержимого будут недоступны для участников.", - "revdelete-text-file": "Удалённые версии файла будут по-прежнему видны в истории страницы, но определенные части их содержимого будут недоступны для участников.", - "logdelete-text": "Удалённые события будут по-прежнему видны в журналах, но определенные части их содержимого будут недоступны для участников.", + "revdelete-text-text": "Удалённые версии будут по-прежнему видны в истории страницы, но определённые части их содержимого будут недоступны для участников.", + "revdelete-text-file": "Удалённые версии файла будут по-прежнему видны в истории страницы, но определённые части их содержимого будут недоступны для участников.", + "logdelete-text": "Удалённые события будут по-прежнему видны в журналах, но определённые части их содержимого будут недоступны для участников.", "revdelete-text-others": "Другие администраторы по-прежнему будут иметь возможность доступа к скрытому содержимому и смогут восстановить его, если не установлены дополнительные ограничения.", "revdelete-confirm": "Пожалуйста, подтвердите, что вы действительно желаете совершить это действие, осознаёте последствия, делаете это в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].", "revdelete-suppress-text": "Сокрытие может производиться только в следующих случаях:\n* потенциально клеветническая информация\n* неуместная личная информация\n*: домашний адрес, номера телефонов, номер паспорта и т. д.", @@ -1060,7 +1061,7 @@ "diff-multi-otherusers": "(не {{PLURAL:$1|показана $1 промежуточная версия|показаны $1 промежуточные версии|показано $1 промежуточных версий}} {{PLURAL:$2|$2 участника|$2 участников}})", "diff-multi-manyusers": "({{PLURAL:$1|не показана $1 промежуточная версия, сделанная|не показаны $1 промежуточные версии, сделанные|не показано $1 промежуточных версий, сделанных}} более чем {{PLURAL:$2|$2 участником|$2 участниками}})", "diff-paragraph-moved-tonew": "Параграф был перемещён. Нажмите, чтобы перейти к новому местоположению.", - "diff-paragraph-moved-toold": "Пункт был перемещен. Нажмите, чтобы перейти к старому местоположению.", + "diff-paragraph-moved-toold": "Пункт был перемещён. Нажмите, чтобы перейти к старому местоположению.", "difference-missing-revision": "Не {{PLURAL:$2|1=найдена|найдены}} {{PLURAL:$2|$2 версия|$2 версий|$2 версии|1=одна из версий}} для этого сравнения ($1).\n\nТакое обычно случается при переходе по устаревшей ссылке сравнения версий для страницы, которая была удалена.\nПодробности могут быть в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале удалений].", "searchresults": "Результаты поиска", "search-filter-title-prefix": "Искать только на страницах, название которых начинается с «$1»", @@ -1574,7 +1575,7 @@ "rcfilters-filter-newpages-label": "Создания страниц", "rcfilters-filter-newpages-description": "Правки, приводящие к созданию новых страниц.", "rcfilters-filter-categorization-label": "Изменения категорий", - "rcfilters-filter-categorization-description": "Записи о страницах, добавленных или удаленных из категорий.", + "rcfilters-filter-categorization-description": "Записи о страницах, добавленных или удалённых из категорий.", "rcfilters-filter-logactions-label": "Протоколируемые действия", "rcfilters-filter-logactions-description": "Административные действия, создания учётных записей, удаления страниц, загрузки файлов…", "rcfilters-hideminor-conflicts-typeofchange-global": "Фильтр \"малые правки\" конфликтует с одним или несколькими фильтрами, поскольку некоторые типы правок не могут быть названы малыми. Конфликтные фильтры отмечены вверху, в области Активных фильтров.", @@ -1670,7 +1671,7 @@ "upload_directory_missing": "Директория для загрузок ($1) отсутствует и не может быть создана веб-сервером.", "upload_directory_read_only": "Веб-сервер не имеет прав записи в папку ($1), в которой предполагается хранить загружаемые файлы.", "uploaderror": "Ошибка загрузки файла", - "upload-recreate-warning": "Внимание: файл с таким именем был удален или переименован.\n\nНиже представлены журналы удалений и переименований этой страницы:", + "upload-recreate-warning": "Внимание: файл с таким именем был удалён или переименован.\n\nНиже представлены журналы удалений и переименований этой страницы:", "uploadtext": "Воспользуйтесь этой формой для загрузки файлов на сервер.\nЧтобы просмотреть ранее загруженные файлы, обратитесь к [[Special:FileList|списку загруженных файлов]]. Загрузка файлов также записывается в [[Special:Log/upload|журнал загрузок]]; данные об удалённых файлах можно найти в [[Special:Log/delete|журнале удалений]].\n\nДля включения файла в статью вы можете использовать строки вида:\n* [[{{ns:file}}:File.jpg]] для вставки полной версии файла;\n* [[{{ns:file}}:File.png|200px|thumb|left|описание]] для вставки слева от текста уменьшенной до 200 пикселей по ширине версии файла с выводом под ним указанного описания;\n* [[{{ns:media}}:File.ogg]] для вставки ссылки на файл, без отображения его содержимого на странице.", "upload-permitted": "{{PLURAL:$2|Разрешённый тип|Разрешённые типы}} файлов: $1.", "upload-preferred": "{{PLURAL:$2|Предпочтительный тип|Предпочтительные типы}} файлов: $1.", @@ -2122,7 +2123,7 @@ "apihelp-no-such-module": "Модуль «$1» не найден.", "apisandbox": "Песочница API", "apisandbox-jsonly": "Для использования API-песочницы требуется JavaScript.", - "apisandbox-api-disabled": "API отключен на этом сайте.", + "apisandbox-api-disabled": "API отключён на этом сайте.", "apisandbox-intro": "Используйте эту страницу для экспериментов с MediaWiki API.\nОбратитесь к документации API ([https://ru.wikipedia.org/w/api.php встроенной] или [[mw:API:Main page|внешней]]) для получения дополнительной информации об использовании API. Например, о том, [https://www.mediawiki.org/wiki/API#A_simple_example как получить содержание Заглавной страницы]. Выберите действие, чтобы увидеть другие примеры.\nОбратите внимание, что, хотя это и песочница, действия, выполненные на этой странице, могут внести изменения в вики.", "apisandbox-submit": "Сделать запрос", "apisandbox-reset": "Очистить", @@ -2390,6 +2391,9 @@ "deleting-backlinks-warning": "Предупреждение: [[Special:WhatLinksHere/{{FULLPAGENAME}}|Другие страницы]] ссылаются на страницу, которую вы собираетесь удалить, или содержат её.", "deleting-subpages-warning": "Предупреждение: У страницы, которую вы собираетесь удалить, имеется [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1 подстраница|$1 подстраницы|$1 подстраниц|51=более 50 подстраниц}}]].", "rollback": "Откатить изменения", + "rollback-confirmation-confirm": "Пожалуйста, подтвердите:", + "rollback-confirmation-yes": "Откатить", + "rollback-confirmation-no": "Отмена", "rollbacklink": "откатить", "rollbacklinkcount": "откатить $1 {{PLURAL:$1|правку|правки|правок}}", "rollbacklinkcount-morethan": "откатить более $1 {{PLURAL:$1|правки|правок}}", @@ -2521,7 +2525,7 @@ "namespace": "Пространство имён:", "invert": "Обратить выбранное", "tooltip-invert": "Установите эту отметку, чтобы скрыть изменения на страницах, в пределах выбранного пространства имён (и связанных пространств имён, если указано)", - "tooltip-whatlinkshere-invert": "Установите этот флажок, чтобы Скрыть ссылки от страниц в выбранном пространстве имен.", + "tooltip-whatlinkshere-invert": "Установите этот флажок, чтобы скрыть ссылки от страниц в выбранном пространстве имён.", "namespace_association": "Связанное пространство", "tooltip-namespace_association": "Установите эту отметку, чтобы также включить пространство имён обсуждения (или предметное), связанное с выбранным пространством имён", "blanknamespace": "(Основное)", @@ -2958,7 +2962,7 @@ "interlanguage-link-title": "$1 — $2", "common.css": "/* Размещённый здесь CSS будет применяться ко всем темам оформления */", "print.css": "/* Размещённый здесь CSS будет применяться к версии для печати */", - "noscript.css": "/* Размещённый здесь CSS будет применяться для участников с отключенным JavaScript */", + "noscript.css": "/* Размещённый здесь CSS будет применяться для участников с отключённым JavaScript */", "group-autoconfirmed.css": "/* Размещённый здесь CSS будет применяться для автоподтверждённых участников */", "group-user.css": "/* Размещённый здесь CSS будет применяться только для зарегистрированных пользователей */", "group-bot.css": "/* Размещённый здесь CSS будет применяться только для ботов */", @@ -3160,8 +3164,8 @@ "confirmemail_invalidated": "Подтверждение адреса электронной почты отменено.", "invalidateemail": "Отмена подтверждения адреса электронной почты", "notificationemail_subject_changed": "Адрес электронной почты для {{SITENAME}} был изменён", - "notificationemail_subject_removed": "{{SITENAME}} зарегистрированный адрес электронной почты был удален", - "notificationemail_body_changed": "Кто-то (вероятно, вы) с IP-адреса $1,\nизменил адрес электронной почты учетной записи «$2» на «$3» на {{SITENAME}}.\n\nЕсли это были не вы, обратитесь к администратору сайта немедленно.", + "notificationemail_subject_removed": "{{SITENAME}} зарегистрированный адрес электронной почты был удалён", + "notificationemail_body_changed": "Кто-то (вероятно, вы) с IP-адреса $1,\nизменил адрес электронной почты учётной записи «$2» на «$3» на {{SITENAME}}.\n\nЕсли это были не вы, обратитесь к администратору сайта немедленно.", "notificationemail_body_removed": "Кто-то (вероятно, вы) с IP-адреса $1\nудалил адрес электронной почты учётной записи «$2» на {{SITENAME}}.\n\nЕсли это были не вы, обратитесь к администратору сайта немедленно.", "scarytranscludedisabled": "[Интервики-включение отключено]", "scarytranscludefailed": "[Ошибка обращения к шаблону $1]", @@ -3728,7 +3732,7 @@ "expand_templates_generate_rawhtml": "Показать HTML", "expand_templates_preview": "Предпросмотр", "expand_templates_preview_fail_html": "Так как {{SITENAME}} разрешает использовать чистый HTML, предварительный просмотр отключён в качестве меры предотвращения JavaScript-атак.\n\nЕсли это добросовестная попытка редактирования, пожалуйста, попробуйте ещё раз.\nЕсли не получается повторная правка, попробуйте [[Special:UserLogout|завершить сеанс]] работы, заново представиться и проверить, что ваш браузер разрешает использовать cookies на этом сайте.", - "expand_templates_preview_fail_html_anon": "Поскольку на сайте {{SITENAME}} включен «сырой» HTML, а вы не авторизовались, предварительный просмотр скрыт в качестве меры предосторожности против JavaScript-атак.\n\nЕсли это правомерная попытка предварительного просмотра, пожалуйста, [[Special:UserLogin|войдите]] и попробуйте ещё раз.", + "expand_templates_preview_fail_html_anon": "Поскольку на сайте {{SITENAME}} включён «сырой» HTML, а вы не авторизовались, предварительный просмотр скрыт в качестве меры предосторожности против JavaScript-атак.\n\nЕсли это правомерная попытка предварительного просмотра, пожалуйста, [[Special:UserLogin|войдите]] и попробуйте ещё раз.", "expand_templates_input_missing": "Вы должны вставить хоть какой-то текст.", "pagelanguage": "Изменение языка страницы", "pagelang-name": "Страница", @@ -3880,7 +3884,7 @@ "authmanager-authn-not-in-progress": "Проверка подлинности не выполняется или данные сессии были утеряны. Пожалуйста, начните снова с самого начала.", "authmanager-authn-no-primary": "Предоставленные учётные данные не могут быть проверены на подлинность.", "authmanager-authn-no-local-user": "Предоставленные учётные данные не связаны ни с одним участником этой вики.", - "authmanager-authn-no-local-user-link": "Предоставленные учётные данные корректны, но не связаны ни с одни участником этой вики. Войдите с помощью какого-то другого способа или создайте новую учётную запись, и у вас появится возможность привязать свои предыдущие учётные данные к этой учетной записи.", + "authmanager-authn-no-local-user-link": "Предоставленные учётные данные корректны, но не связаны ни с одни участником этой вики. Войдите с помощью какого-то другого способа или создайте новую учётную запись, и у вас появится возможность привязать свои предыдущие учётные данные к этой учётной записи.", "authmanager-authn-autocreate-failed": "Автоматическое создание локальной учётной записи не удалось: $1", "authmanager-change-not-supported": "Предоставленные учётные данные не могут быть изменены, так как они не будут использованы.", "authmanager-create-disabled": "Создание учётных записей отключено.", @@ -3941,10 +3945,10 @@ "cannotlink-no-provider-title": "Нет связываемых учётных записей", "cannotlink-no-provider": "Нет связываемых учётных записей.", "linkaccounts": "Связать учётные записи", - "linkaccounts-success-text": "Учетная запись была связана.", + "linkaccounts-success-text": "Учётная запись была связана.", "linkaccounts-submit": "Связать учётные записи", "unlinkaccounts": "Отвязать учётные записи", - "unlinkaccounts-success": "Учетная запись была отвязан.", + "unlinkaccounts-success": "Учётная запись была отвязан.", "authenticationdatachange-ignored": "Изменение данных для проверки подлинности не было обработано. Может быть, не был настроен ни один провайдер?", "userjsispublic": "Обратите внимание: подстраницы JavaScript не должны содержать конфиденциальные сведения, поскольку они доступны для просмотра другим участникам.", "userjsonispublic": "Обратите внимание: подстраницы JSON не должны содержать конфиденциальных данных, поскольку они доступны для просмотра другими участниками.", @@ -3961,8 +3965,8 @@ "gotointerwiki": "Покидаем {{grammar:accusative|{{SITENAME}}}}...", "gotointerwiki-invalid": "Указан некорректный заголовок.", "gotointerwiki-external": "Вы покидаете {{grammar:accusative|{{SITENAME}}}} для посещения стороннего сайта [[$2]].\n\n'''[$1 Перейти на $1]'''", - "undelete-cantedit": "Вы не можете восстановить эту страницу, поскольку у вас недостаточно прав для ее редактирования.", - "undelete-cantcreate": "Вы не можете восстановить эту страницу, поскольку она не существует, а у вас недостаточно прав для ее создания.", + "undelete-cantedit": "Вы не можете восстановить эту страницу, поскольку у вас недостаточно прав для её редактирования.", + "undelete-cantcreate": "Вы не можете восстановить эту страницу, поскольку она не существует, а у вас недостаточно прав для её создания.", "pagedata-title": "Данные страницы", "pagedata-text": "Эта страница предоставляет интерфейс к данным страниц. Пожалуйста, введите заголовок страницы в URL, используя синтаксис подстраниц.\n* Согласование содержимого применяется основываясь на заголовке Accept вашего клиента. Это означает, что данные страницы будут предоставлены в формате, предпочитаемом вашим клиентом.", "pagedata-not-acceptable": "Соответствующий формат не найден. Поддерживаемые MIME-типы: $1", @@ -3981,5 +3985,5 @@ "passwordpolicies-policy-passwordnotinlargeblacklist": "Пароль не может соответствовать какому-либо из 100 000 самых часто используемых паролей.", "passwordpolicies-policyflag-forcechange": "необходимо изменить при входе", "easydeflate-invaliddeflate": "Предоставленное содержимое не спущено надлежащим образом", - "unprotected-js": "По соображениям безопасности JavaScript нельзя загружать с незащищенных страниц. Пожалуйста, создавайте скрипты только в пространстве имён MediaWiki: или как подстраницы участника." + "unprotected-js": "По соображениям безопасности JavaScript нельзя загружать с незащищённых страниц. Пожалуйста, создавайте скрипты только в пространстве имён MediaWiki: или как подстраницы участника." } diff --git a/languages/i18n/sah.json b/languages/i18n/sah.json index 03b66bb545..fbbfed51b2 100644 --- a/languages/i18n/sah.json +++ b/languages/i18n/sah.json @@ -420,7 +420,7 @@ "userlogout": "Тахсыы", "notloggedin": "Ааккын эппэтиҥ", "userlogin-noaccount": "Бэлиэтэнэ иликкин дуо?", - "userlogin-joinproject": "{{SITENAME}} ситим-сиргэ киирии", + "userlogin-joinproject": "{{SITENAME}} ситим-сиргэ бэлиэтэнии", "createaccount": "Бэлиэтэнии", "userlogin-resetpassword-link": "Аһарык тылгын санаттараҕын дуо?", "userlogin-helplink2": "Киирэргэ көмө", @@ -642,7 +642,7 @@ "subject-preview": "Аата маннык көстүөҕэ:", "previewerrortext": "Уларытыыгын бигэргэтиэх иннинэ көрдөрөргө алҕас таҕыста.", "blockedtitle": "Кыттааччы уларытар кыаҕа быһылынна", - "blockedtext": "'''Эн аатыҥ эбэтэр IP-аадырыһыҥ бобулуннулар.'''\n\nБоппут киһи $1.\nТөрүөтэ: ''«$2»''.\n\n*Бобуллубут: $8\n*Бобуу болдьоҕо: $6\n*Бобулунна: $7\n\nЭн $1 диэн киһиэхэ эбэтэр атын [[{{MediaWiki:Grouppage-sysop}}|администраатарга]] суруйан быһаарсыаххын сөп.\nБолҕой, өскө регистрацияламматах буоллаххына, эбэтэр эл. аадырыскын [[Special:Preferences|бигэргэппэтэх]] буоллаххына, эбэтэр сурук суруйарыҥ бобуллубут буоллаҕына администраатарга суруйар кыаҕыҥ суох.\nЭн IP-аадырыһыҥ — $3, бобуу нүөмэрэ — #$5.\nОну суруккар киллэрээр.", + "blockedtext": "'''Эн бэлиэ-аатыҥ эбэтэр IP-аадырыһыҥ бобуллубуттар.'''\n\nБоппут дьаһабыл - $1.\nТөрүөтүн маны ыйыбыт: $2.\n\n*Бобуллубут күнэ-ыйа: $8\n*Бобуу болдьоҕо: $6\n*Бобуу соруга: $7\n\nЭн $1 диэн киһиэхэ эбэтэр атын [[{{MediaWiki:Grouppage-sysop}}|дьаһабылга]] суруйан, быһаарсыаххын сөп.\nБолҕой, өскөтө эл. аадырыскын [[Special:Preferences|туруорууларгар]] эппэтэх эбэтэр бигэргэппэтэх буоллаххына, эбэтэр боппут киһи сурук суруйаргын боппут буоллаҕына, \"{{int:emailuser}}\" функцияны туһанар кыаҕыҥ суох.\nЭн IP-аадырыһыҥ — $3, бобуу нүөмэрэ — #$5.\nМаны суруккар ыйаар.", "autoblockedtext": "Эн IP-аадырыскын ханнык эрэ бу бырайыакка кыттара бобуллубут киһи туһана сылдьыбыт, онон бу IP-аадырыс бобуулаах. Боппут администраатар ($1) ол төрүөтүн маннык суруйбут:\n\n:''$2''\n\n*Бобуллубут: $8\n*Бобуу болдьоҕо: $6\n*Бобулунна: $7\n\nЭн $1 диэн киһиэхэ эбэтэр атын [[{{MediaWiki:Grouppage-sysop}}|администраатарга]] сурук суруйан быһаарсыаххын сөп.\n\nБолҕой, өскө регистрацияламматах буоллаххына, эбэтэр эл. аадырыскын [[Special:Preferences|бигэргэппэтэх]] буоллаххына, эбэтэр сурук суруйарыҥ бобуллубут буоллаҕына администраатарга суруйар кыаҕыҥ суох.\n\nIP-аадырыһыҥ $3, бобуу нүөмэрэ — #$5.\nОну суруккар киллэрээр.", "systemblockedtext": "Бэлиэ-ааккын эбэтэр IP-аадырыскын MediaWiki хааччахтаабыт.\nЫйыллыбыт төрүөтэ:\n\n:$2\n\n* Хааччах саҕаланыыта: $8\n* Хааччах уһуллуута: $6\n* Бүөлээһин соруга: $7\n\nЭн билиҥҥи IP-аадырыһыҥ $3.\nБыһаарсар буоллаххына, бу сибидиэнньэлэри этээр дуу.", "blockednoreason": "биир да биричиинэ сөп түбэспэт", @@ -657,7 +657,7 @@ "accmailtext": "[[User talk:$1|$1]] кыттааччыга түбэспиччэ бэлиэлэртэн оҥоһуллубут аһарык тыл бу аадырыска $2 ыытылынна.\nТиһиккэ бэлиэтэнэн баран аһарыккын ''[[Special:ChangePassword|уларытыаххын]]'' сөп.", "newarticle": "(Саҥа ыстатыйа)", "newarticletext": "Эн суох сирэйгэ киирэ сатаатыҥ.\nМаннык ааттаах саҥа ыстатыйаны оҥорор буоллаххына, аллара баар түннүккэ суруй\n(сиһ. [$1 көмөнү] көрүөххүн сөп).\nӨскө манна сыыһа киирбит буоллаххына интэриниэтиҥ бырагыраамматын \"төнүн\" диэххин сөп.", - "anontalkpagetext": "----\nБу аатын эппэтэх, бэлиэтэнэ илик эбэтэр бэлиэтэниэн баҕарбат кыттааччы сирэйэ.\nОл иһин кинини чопчулаары IP-аадырыһын туттабыт.\nБу аадырыһы атын кыттааччылар эмиэ туһаныахтарын сөп.\nӨскөтө Эн ааккын эппэтэх кыттааччы Эйиэхэ туһуламматах суругу туттум дии саныыр буоллаххына, бука диэн, [[Special:CreateAccount|бэлиэтэн]] биитэр [[Special:UserLogin|урут бэлиэтэммит ааккынан киир]]. Оччоҕо булкуур тахсыа суоҕа.", + "anontalkpagetext": "----\nБу аатын эппэтэх, бэлиэтэнэ илик эбэтэр бэлиэтэниэн баҕарбат кыттааччы сирэйэ.\nОл иһин кинини чопчулаары IP-аадырыһын туттабыт.\nБу аадырыһы атын кыттааччылар эмиэ туһаныахтарын сөп.\nӨскөтө Эн, ааккын эппэтэх кыттааччы буоллаххына, Эйиэхэ туһуламматах суругу туттум дии саныыр буоллаххына, бука диэн, [[Special:CreateAccount|бэлиэтэн]] биитэр [[Special:UserLogin|урут бэлиэтэммит ааккынан киир]]. Оччоҕо булкуур тахсыа суоҕа.", "noarticletext": "Билигин бу сирэй кураанах.\nБу аат атын ыстатыйаларга туттулларын [[Special:Search/{{PAGENAME}}|булуоххун сөп]],\n[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} сурунаалларга көрдүөххүн сөп],\nэбэтэр [{{fullurl:{{FULLPAGENAME}}|action=edit}} маннык ааттаах саҥа ыстатыйаны суруйуоххун сөп].", "noarticletext-nopermission": "Билигин бу сирэй кураанах.\nБу [[Special:Search/{{PAGENAME}}|ааты атын сирэйдэргэ көрдөөн көрүөххүн]] сөп,\nэбэтэр [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} сурунаалларга манна сыһыаннаах суруктары булуоххун сөп]. Бу сирэйи айар кыаҕыҥ суох.", "missing-revision": "«{{FULLPAGENAME}}» сирэй $1 барыла суох.\n\nМаннык үксүн хайыы-үйэ сотуллубут билэҕэ эргэрбит сигэнэн бардахха буолааччы.\nСиһилии баҕар [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} сотуу сурунаалыгар] баара буолуо.", @@ -920,6 +920,7 @@ "diff-paragraph-moved-toold": "Параграф көһөрүллүбүт. Баттаан урукку сиригэр көс.", "difference-missing-revision": "$2 барыл бу тэҥнээһиҥҥэ ($1) көстүбэтэ.\n\nБу үксүн хайыы-үйэ сотуллубут сирэйи кытта тэҥнээри эргэрбит сигэнэн кэллэххэ баар буолааччы.\nСиһилии баҕар [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} сотуу сурунаалыгар] баара буолуо.", "searchresults": "Булулунна", + "search-filter-title-prefix-reset": "Сирэйдэри барытын көрдөөһүн", "searchresults-title": "Көрдөөһүн түмүгэ \"$1\"", "titlematches": "Ыстатыйалар ааттара хоһулаһар", "textmatches": "Ыстатыйалар истэрэ хатыланар", @@ -994,6 +995,7 @@ "prefs-watchlist-edits": "Кэтээбил тиһигэр уларытыы көстүүтүн муҥутуур ахсаана:", "prefs-watchlist-edits-max": "Улааппыта: 1000", "prefs-watchlist-token": "Кэтэбил тиһигин бэлиэтэ (токен):", + "prefs-watchlist-managetokens": "Токеннары салайыы", "prefs-misc": "Атын туруоруулар", "prefs-resetpass": "Кирии тылы уларытыы", "prefs-changeemail": "Аадырыһы уларытыы уонна сотуу", @@ -1072,6 +1074,7 @@ "prefs-dateformat": "Күн-дьыл көрүҥэ (формаата)", "prefs-timeoffset": "Кэм уратыта", "prefs-advancedediting": "Сүрүн туруоруулар", + "prefs-developertools": "Оҥорооччу тэриллэрэ", "prefs-editor": "Эрэдээктэр", "prefs-preview": "Инники көрүү", "prefs-advancedrc": "Дириҥэтиллибит туруоруулар", @@ -1080,6 +1083,8 @@ "prefs-advancedwatchlist": "Дириҥэтиллибит туруоруулар", "prefs-displayrc": "Туруоруулары көрдөр", "prefs-displaywatchlist": "Көстүүтүн туруоруулара", + "prefs-changesrc": "Көстүбүт уларытыылар", + "prefs-changeswatchlist": "Көрдөр;ллэр уларытыылар", "prefs-tokenwatchlist": "Токен", "prefs-diffs": "Уратылара", "prefs-help-prefershttps": "Аныгыскы киириигэр үлэлиир буолуо.", @@ -1478,7 +1483,7 @@ "recentchangeslinked-feed": "Сигэнэр уларытыылар", "recentchangeslinked-toolbox": "Сигэнэр уларытыылар", "recentchangeslinked-title": "\"$1\" кытта сибээстээх уларытыылар", - "recentchangeslinked-summary": "Сирэй аатын суруйдаххына киниэхэ эбэтэр кини сигэнэр сирэйдэрин уларытыы көстүөҕэ. (Категорияҕа киирэрэ сирэйдэри көрөргө маннык суруй: Category:Категория аата). [[Special:Watchlist|Кэтиир сирэйдэриҥ]] уларыйыыта модьу бичигинэн бэлиэтэммит.", + "recentchangeslinked-summary": "Сирэй аатын суруйдаххына киниэхэ эбэтэр кини сигэнэр сирэйдэрин уларытыы көстүөҕэ. (Категорияҕа киирэр сирэйдэри көрөргө маннык суруй: {{ns:category}}:Категория аата). [[Special:Watchlist|Кэтиир сирэйдэриҥ]] уларыйыыта модьу бичигинэн бэлиэтэнэр.", "recentchangeslinked-page": "Сирэй аата:", "recentchangeslinked-to": "Төттөрүтүн, ыйыллыбыт сирэйгэ сигэнэр сирэйдэри көрдөр", "recentchanges-page-added-to-category": "[[:$1]] категорияҕа эбилиннэ", @@ -1752,9 +1757,9 @@ "filehist-filesize": "Билэ кээмэйэ", "filehist-comment": "Хос быһаарыы", "imagelinks": "Билэни туттуу", - "linkstoimage": "Бу билэҕэ маннык атын {{PLURAL:$1|сирэй сигэнэр|$1 сирэйдэр сигэнэллэр}}:", - "linkstoimage-more": "$1 {{PLURAL:$1|сирэйтэн|сирэйтэн}} элбэх сирэй бу билэҕэ сигэнэр.\nОнтон бу тиһиккэ {{PLURAL:$1|$1 эрэ сигэ көһүннэ|$1 сигэ эрэ көһүннэ}}.\nӨссө [[Special:WhatLinksHere/$2|толору тиһиги]] көрүөххүн сөп.", - "nolinkstoimage": "Атын сирэйдэр бу билэҕэ сигэммэттэр.", + "linkstoimage": "Бу билэни бу {{PLURAL:$1|сирэй туһанар|$1 сирэйдэр туһаналлар}}:", + "linkstoimage-more": "$1 {{PLURAL:$1|сирэйтэн|сирэйтэн}} элбэх сирэй бу билэни туһанар.\nБу испииһэккэ {{PLURAL:$1|$1 эрэ туһанар сирэй көрдөрүлүннэ|$1 эрэ туһанар сирэй көрдөрүлүннэ}}.\nЭбии [[Special:WhatLinksHere/$2|толору испииһэги]] көрүөххүн сөп.", + "nolinkstoimage": "Атын сирэйдэр бу билэни туһамматтар.", "morelinkstoimage": "Бу билэҕэ [[Special:WhatLinksHere/$1|атын сигэлэри]] көрөргө.", "linkstoimage-redirect": "$1 (утаарыы-билэ) $2", "duplicatesoffile": "Бу билэ {{PLURAL:$1|дубликаата манна көстөр|$1 дубликаата манна көстөллөр}} ([[Special:FileDuplicateSearch/$2|сиһилии]]):", diff --git a/languages/i18n/sk.json b/languages/i18n/sk.json index ae38bab099..546fa8bc8d 100644 --- a/languages/i18n/sk.json +++ b/languages/i18n/sk.json @@ -39,7 +39,8 @@ "Pmikolas44", "Fitoschido", "Matěj Suchánek", - "Vlad5250" + "Vlad5250", + "Robert Važan" ] }, "tog-underline": "Podčiarkovať odkazy:", @@ -88,7 +89,7 @@ "underline-always": "Vždy", "underline-never": "Nikdy", "underline-default": "Podľa nastavení prehliadača alebo témy vzhľadu", - "editfont-style": "Štýl písma oblasti na úpravy:", + "editfont-style": "Štýl písma v editore:", "editfont-monospace": "S pevnou šírkou znaku", "editfont-sansserif": "Bezpätkové písmo", "editfont-serif": "Pätkové písmo", @@ -571,7 +572,7 @@ "resetpass-expired": "Platnosť vášho hesla vypršala. Pre prihlásenie si nastavte nové heslo.", "resetpass-expired-soft": "Platnosť vášho hesla vypršala, musíte si nastaviť nové. Zvoľte si nové heslo nebo kliknite na „{{int:authprovider-resetpass-skip-label}}“ a nastavte si ho neskôr.", "resetpass-validity-soft": "Vaše heslo je neplatné: $1\n\nVyberte si nové heslo, alebo kliknite na „{{int:authprovider-resetpass-skip-label}}“ a nastavte si ho neskôr.", - "passwordreset": "Reset hesla", + "passwordreset": "Obnova hesla", "passwordreset-text-one": "Pre získanie nového hesla vyplňte tento formulár.", "passwordreset-text-many": "{{PLURAL:$1|Pre získanie nového hesla e-mailom, zadajte jeden z údajov.}}", "passwordreset-disabled": "Obnovenie hesla bolo na tejto wiki zakázané.", @@ -601,7 +602,7 @@ "changeemail-submit": "Zmeniť e-mail", "changeemail-throttled": "Uskutočnili ste príliš mnoho neúspešných pokusov o prihlásenie. Prosím, počkajte $1 predtým, než to skúsite znova.", "changeemail-nochange": "Zadajte prosím odlišnú e-mailovú adresu.", - "resettokens": "Obnoviť tokeny", + "resettokens": "Reinicializovať kľúče", "resettokens-text": "Tu môžete obnoviť tokeny, ktoré umožňujú prístup k určitým súkromným údajom spojeným s vaším účtom.\n\nMali by ste to urobiť, ak ste ich omylom niekomu poskytli alebo ak bolo vaše konto zneužité.", "resettokens-no-tokens": "Neexistujú žiadne tokeny, ktoré by bolo možné obnoviť.", "resettokens-tokens": "Tokeny:", @@ -981,7 +982,8 @@ "prefs-watchlist-days-max": "Najviac $1 {{PLURAL:$1|deň|dni|dní}}", "prefs-watchlist-edits": "Počet úprav, ktorý sa zobrazí v rozšírenom zozname sledovaných:", "prefs-watchlist-edits-max": "Maximum: 1000", - "prefs-watchlist-token": "Token zoznamu sledovaných?", + "prefs-watchlist-token": "Kľúč pre sledovanie stránok", + "prefs-watchlist-managetokens": "Správa kľúčov", "prefs-misc": "Rôzne", "prefs-resetpass": "Zmeniť heslo", "prefs-changeemail": "Zmeniť alebo odstrániť e-mailovú adresu", @@ -1000,6 +1002,7 @@ "recentchangescount": "Štandardne zobrazovaný počet úprav:", "prefs-help-recentchangescount": "Toto sa týka posledných úprav, histórií stránok a záznamov.", "prefs-help-watchlist-token2": "Toto je tajný kľúč k webovému kanálu vášho zoznamu sledovaných stránok.\nkaždý, kto ho pozná si bude môcť prečítať váš zoznam sledovaných stránok, preto ho nezverejňujte.\n[[Special:ResetTokens|Kliknite sem, ak potrebujete vytvoriť nový]].", + "prefs-help-tokenmanagement": "Môžete vidieť a reinicializovať tajný kľúč k vašemu účtu, ktorý poskytuje prístup k webovému kanálu vášho zoznamu sledovaných stránok. Ktokoľvek, kto pozná tento kľúč, môže čítať váš zoznam sledovaných stránok, tak kľúč nezdieľajte.", "savedprefs": "Vaše nastavenia boli uložené.", "savedrights": "Práva {{GENDER:$1|používateľa|používateľky}} $1 boli uložené.", "timezonelegend": "Časové pásmo:", @@ -1030,7 +1033,7 @@ "prefs-common-config": "Spoločné CSS/JS pre všetky témy:", "prefs-reset-intro": "Túto stránku môžete použiť na vrátenie predvolených hodnôt vašich nastavení.\nTúto operáciu nemožno vrátiť.", "prefs-emailconfirm-label": "Overenie e-mailu:", - "youremail": "Váš e-mail²", + "youremail": "Email:", "username": "{{GENDER:$1|Používateľské meno}}:", "prefs-memberingroups": "{{GENDER:$2|Člen|Členovia}} {{PLURAL:$1|skupiny|skupín}}:", "group-membership-link-with-expiry": "$1 (do $2)", @@ -1060,7 +1063,7 @@ "prefs-timeoffset": "Časový posun", "prefs-advancedediting": "Všeobecné možnosti", "prefs-developertools": "Vývojárske nástroje", - "prefs-editor": "Používateľ", + "prefs-editor": "Editor", "prefs-preview": "Náhľad", "prefs-advancedrc": "Rozšírené možnosti", "prefs-advancedrendering": "Rozšírené možnosti", @@ -1068,7 +1071,9 @@ "prefs-advancedwatchlist": "Rozšírené možnosti", "prefs-displayrc": "Možnosti zobrazenia", "prefs-displaywatchlist": "Možnosti zobrazenia", - "prefs-tokenwatchlist": "Token", + "prefs-changeswatchlist": "Zobrazené zmeny", + "prefs-pageswatchlist": "Sledované stránky", + "prefs-tokenwatchlist": "Kľúč", "prefs-diffs": "Rozdiely", "prefs-help-prefershttps": "Táto voľba sa prejaví pri vašom ďalšom prihlásení.", "prefswarning-warning": "Vykonali ste zmeny v nastaveniach, ktoré zatiaľ nie sú uložené. Ak túto stránku opustíte bez kliknutia na „$1“, vaše nastavenia sa neaktualizujú.", @@ -1302,6 +1307,7 @@ "rcfilters-other-review-tools": "Ďalšie kontrolné nástroje", "rcfilters-group-results-by-page": "Zoskupiť výsledky podľa stránky", "rcfilters-activefilters": "Aktívne filtre", + "rcfilters-activefilters-hide": "Skryť", "rcfilters-advancedfilters": "Pokročilé filtre", "rcfilters-limit-title": "Zobraziť zmeny", "rcfilters-limit-and-date-label": "{{PLURAL:$1|Jedna úprava|$1 úpravy|$1 úprav}}, $2", @@ -1325,6 +1331,7 @@ "rcfilters-savedqueries-apply-and-setdefault-label": "Vytvoriť predvolený filter", "rcfilters-savedqueries-cancel-label": "Zrušiť", "rcfilters-savedqueries-add-new-title": "Uložiť súčasné nastavenie filtrov", + "rcfilters-savedqueries-already-saved": "Tieto filtre sú už uložené. Zmeňte niektoré nastavenia, ak chcete vytvoriť nový uložený filter.", "rcfilters-restore-default-filters": "Obnoviť predvolené filtre", "rcfilters-clear-all-filters": "Zrušiť všetky filtre", "rcfilters-show-new-changes": "Zobraziť najnovšie zmeny", @@ -1458,7 +1465,7 @@ "recentchangeslinked-feed": "Súvisiace úpravy", "recentchangeslinked-toolbox": "Súvisiace úpravy", "recentchangeslinked-title": "Zmeny týkajúce sa „$1”", - "recentchangeslinked-summary": "Vložte názov článku, aby ste videli zmeny stránok, na ktoré odkazuje alebo ktoré odkazujú na danú stránku (aby ste videli články v kategórii, zadajte Kategória:Názov kategórie).\nStránky, ktoré sa nachádzajú vo vašom [[Special:Watchlist|zozname sledovaných]] sú vyznačené hrubo.", + "recentchangeslinked-summary": "Vložte názov stránky, aby ste videli zmeny iných stránok, na ktoré odkazuje alebo ktoré odkazujú na danú stránku. (Ak chcete vidieť stránky v kategórii, zadajte {{ns:category}}:Názov kategórie).\nStránky, ktoré sa nachádzajú vo vašom [[Special:Watchlist|zozname sledovaných]] sú vyznačené hrubo.", "recentchangeslinked-page": "Názov stránky:", "recentchangeslinked-to": "Zobraziť zmeny na stránkach, ''ktoré odkazujú na'' zadanú stránku", "recentchanges-page-added-to-category": "[[:$1]] zaradená do kategórie", @@ -1696,9 +1703,9 @@ "filehist-filesize": "veľkosť súboru", "filehist-comment": "komentár", "imagelinks": "Použitie súboru", - "linkstoimage": "Na tento obrázok {{PLURAL:$1|odkazuje nasledujúca stránka|odkazujú nasledujúce $1 stránky|odkazuje nasledujúcich $1 stránok}}:", + "linkstoimage": "Na tento súbor {{PLURAL:$1|odkazuje nasledujúca stránka|odkazujú nasledujúce $1 stránky|odkazuje nasledujúcich $1 stránok}}:", "linkstoimage-more": "Viac ako $1 {{PLURAL:$1|stránka odkazuje|stránky odkazujú|stránok odkazuje}} na tento súbor.\nNasledovný zoznam zobrazuje {{PLURAL:$1|prvú stránku odkazujúcu|prvé $1 stránky odkazujúce|prvých $1 stránok odkazujúcich}} iba na tento súbor.\nMôžete si pozrieť [[Special:WhatLinksHere/$2|úplný zoznam]].", - "nolinkstoimage": "Žiadne stránky neobsahujú odkazy na tento obrázok.", + "nolinkstoimage": "Žiadne stránky neobsahujú odkazy na tento súbor.", "morelinkstoimage": "Zobraziť [[Special:WhatLinksHere/$1|ďalšie odkazy]] na tento súbor.", "linkstoimage-redirect": "$1 (presmerovanie súboru) $2", "duplicatesoffile": "{{PLURAL:$1|Nasledujúci súbor je duplikát|Nasledujúce $1 súbory sú duplikáty||Nasledujúcich $1 súborov sú duplikáty}} tohto súboru ([[Special:FileDuplicateSearch/$2|podrobnosti]]):", @@ -1903,6 +1910,8 @@ "speciallogtitlelabel": "Cieľ (názov alebo {{ns:user}}:Používateľské meno):", "log": "Záznamy", "logeventslist-submit": "Zobraziť", + "logeventslist-patrol-log": "Kniha preverených úprav", + "logeventslist-tag-log": "Kniha značiek", "all-logs-page": "Všetky verejné záznamy", "alllogstext": "Kombinované zobrazenie všetkých dostupných záznamov {{GRAMMAR:genitív|{{SITENAME}}}}.\nMôžete zúžiť rozsah, ak zvolíte typ záznamu, používateľské meno alebo dotyčnú stránku (záleží na veľkosti písmen).", "logempty": "V zázname neboli nájdené zodpovedajúce položky.", @@ -1970,6 +1979,7 @@ "listgrouprights-namespaceprotection-header": "Obmedzenia menných priestorov", "listgrouprights-namespaceprotection-namespace": "Menný priestor", "listgrouprights-namespaceprotection-restrictedto": "Práva umožňujúce redaktorovi upravovať", + "listgrants": "Skupiny oprávnení", "trackingcategories": "Sledovacie kategórie", "trackingcategories-summary": "Táto stránka obsahuje zoznam sledovacích kategórií, ktoré automaticky pridáva softvér MediaWiki. Ich mená je možné zmeniť úpravou príslušných systémových hlásení v mennom priestore {{ns:8}}.", "trackingcategories-msg": "Sledovacia kategória", @@ -2089,6 +2099,7 @@ "deletionlog": "záznam zmazaní", "log-name-create": "Záznam vytvorení stránok", "log-description-create": "Toto je zoznam posledných vytvorených stránok.", + "logentry-create-create": "$1 {{GENDER:$2|vytvoril|vytvorila|vytvoril(a)}} stránku $3", "reverted": "Obnovené na skoršiu verziu", "deletecomment": "Dôvod:", "deleteotherreason": "Iný/ďalší dôvod:", @@ -2677,6 +2688,7 @@ "pageinfo-display-title": "Zobrazovaný názov", "pageinfo-default-sort": "Predvolený kľúč zoraďovania:", "pageinfo-length": "Dĺžka stránky (v bajtoch)", + "pageinfo-namespace": "Menný priestor", "pageinfo-article-id": "ID stránky", "pageinfo-language": "Jazyk obsahu stránky", "pageinfo-language-change": "zmeniť", @@ -2718,6 +2730,7 @@ "pageinfo-category-subcats": "Počet podkategórií", "pageinfo-category-files": "Počet súborov", "pageinfo-user-id": "ID používateľa", + "pageinfo-view-protect-log": "Zobraziť knihu zamknutí tejto stránky.", "markaspatrolleddiff": "Označiť ako preverené", "markaspatrolledtext": "Označiť túto stránku ako preverenú", "markaspatrolledtext-file": "Označiť túto revíziu súboru ako preverenú", @@ -2971,7 +2984,7 @@ "specialpages": "Špeciálne stránky", "specialpages-note-top": "Legenda", "specialpages-note-restricted": "* Bežné špeciálne stránky.\n* Špeciálne stránky s obmedzeným prístupom.", - "specialpages-group-maintenance": "Údržbové správy", + "specialpages-group-maintenance": "Údržba", "specialpages-group-other": "Iné špeciálne stránky", "specialpages-group-login": "Prihlásenie / registrácia", "specialpages-group-changes": "Posledné úpravy a záznamy", @@ -3050,7 +3063,7 @@ "compare-invalid-title": "Názov, ktorý ste zadali nie je platný.", "compare-title-not-exists": "Názov, ktorý ste zadali neexistuje.", "compare-revision-not-exists": "Revízia, ktorú ste zadali, neexistuje.", - "diff-form": "'''formulár'''", + "diff-form": "Rozdiely", "dberr-problems": "Prepáčte! Táto stránka má práve technické problémy.", "dberr-again": "Skúste niekoľko minút počkať a potom opäť načítať stránku.", "dberr-info": "(Spojenie s databázovým serverom neúspešné: $1)", @@ -3234,7 +3247,7 @@ "json-error-unsupported-type": "Odovzdaná hodnota nekódovateľného typu", "headline-anchor-title": "Odkaz na túto sekciu", "special-characters-group-latin": "Latinka", - "special-characters-group-latinextended": "Latina rozšírené", + "special-characters-group-latinextended": "Rozšírenie latinky", "special-characters-group-ipa": "IPA", "special-characters-group-symbols": "Symboly", "special-characters-group-greek": "Grécke", @@ -3253,6 +3266,7 @@ "special-characters-group-thai": "Thajské", "special-characters-group-lao": "Laoské", "special-characters-group-khmer": "Khmer", + "special-characters-group-canadianaboriginal": "Písmo pôvodných obyvateľov Kanady", "special-characters-title-endash": "pomlčka", "special-characters-title-emdash": "dlhá pomlčka", "special-characters-title-minus": "mínus", @@ -3275,5 +3289,6 @@ "changecredentials": "Zmena prihlasovacích údajov", "removecredentials": "Odstránenie prihlasovacích údajov", "userjsispublic": "Uvedomte si prosím, že podstránky s JavaScriptom by nemali obsahovať tajné údaje, pretože sú viditeľné ostatným používateľom.", - "usercssispublic": "Uvedomte si prosím, že podstránky s CSS by nemali obsahovať tajné údaje, pretože sú viditeľné ostatným používateľom." + "usercssispublic": "Uvedomte si prosím, že podstránky s CSS by nemali obsahovať tajné údaje, pretože sú viditeľné ostatným používateľom.", + "passwordpolicies": "Pravidlá pre heslá" } diff --git a/languages/i18n/so.json b/languages/i18n/so.json index 4288c4acfe..cc9ea0798a 100644 --- a/languages/i18n/so.json +++ b/languages/i18n/so.json @@ -6,7 +6,8 @@ "Mimursal", "Yariiska", "아라", - "Macofe" + "Macofe", + "Amire80" ] }, "tog-underline": "Linkiga hoos ka calaamadeysan:", @@ -1086,7 +1087,7 @@ "version-poweredby-others": "kuwa kale", "redirect-file": "Magaca faylka", "fileduplicatesearch-submit": "Raadi", - "specialpages": "bogagga khaaska ah", + "specialpages": "Bogagga khaaska ah", "specialpages-note-top": "Furaha", "specialpages-group-pages": "liiska maqaalada", "blankpage": "Bog masaxan", diff --git a/languages/i18n/sr-ec.json b/languages/i18n/sr-ec.json index b015010a2d..6dca443ded 100644 --- a/languages/i18n/sr-ec.json +++ b/languages/i18n/sr-ec.json @@ -53,46 +53,46 @@ "tog-numberheadings": "Аутоматски нумериши наслове", "tog-editondblclick": "Омогући уређивање страница двоструким кликом", "tog-editsectiononrightclick": "Омогући уређивање одељака десним кликом на њихове наслове", - "tog-watchcreations": "Додај странице које направим и датотеке које отпремим на мој списак надгледања", - "tog-watchdefault": "Додај странице и датотеке које уредим на мој списак надгледања", - "tog-watchmoves": "Додај странице и датотеке које преместим на мој списак надгледања", - "tog-watchdeletion": "Додај странице и датотеке које избришем на мој списак надгледања", - "tog-watchuploads": "Додај нове датотеке које отпремим на мој списак надгледања", - "tog-watchrollback": "Додај странице на којима сам извршио враћање измена на мој списак надгледања", + "tog-watchcreations": "Додаји странице које направим и датотеке које отпремим на мој списак надгледања", + "tog-watchdefault": "Додаји странице и датотеке које уредим на мој списак надгледања", + "tog-watchmoves": "Додаји странице и датотеке које преместим на мој списак надгледања", + "tog-watchdeletion": "Додаји странице и датотеке које избришем на мој списак надгледања", + "tog-watchuploads": "Додаји нове датотеке које отпремим на мој списак надгледања", + "tog-watchrollback": "Додаји странице на којима сам извршио враћање измена на мој списак надгледања", "tog-minordefault": "Подразумевано означавај све измене као мање", "tog-previewontop": "Приказуј претпреглед пре оквира за уређивање", "tog-previewonfirst": "Приказуј претпреглед при првој измени", - "tog-enotifwatchlistpages": "Пошаљи ми е-поруку када се промени страница или датотека са мог списка надгледања", + "tog-enotifwatchlistpages": "Шаљи ми е-поруку када се промени страница или датотека са мог списка надгледања", "tog-enotifusertalkpages": "Пошаљи ми е-поруку кад се промени моја корисничка страница за разговор", - "tog-enotifminoredits": "Пошаљи ми е-поруку и код мањих измена страница и датотека", - "tog-enotifrevealaddr": "Откриј моју адресу е-поште у е-порукама за обавештавање", + "tog-enotifminoredits": "Шаљи ми е-поруку и код мањих измена страница и датотека", + "tog-enotifrevealaddr": "Откриј моју е-адресу у е-порукама за обавештавање", "tog-shownumberswatching": "Прикажи број корисника који надгледају", "tog-oldsig": "Ваш постојећи потпис:", "tog-fancysig": "Сматрај потпис као викитекст (без аутоматског повезивања)", "tog-uselivepreview": "Приказуј претпреглед без поновног учитавања странице", - "tog-forceeditsummary": "Упозори ме када не унесем опис измене", + "tog-forceeditsummary": "Упозоравај ме када не унесем опис измене", "tog-watchlisthideown": "Сакривај моје измене са списка надгледања", "tog-watchlisthidebots": "Сакривај измене ботова са списка надгледања", "tog-watchlisthideminor": "Сакривај мање измене са списка надгледања", "tog-watchlisthideliu": "Сакривај измене пријављених корисника са списка надгледања", - "tog-watchlistreloadautomatically": "Аутоматски поново учитај списак надгледања кад год се филтер промени (потребан јаваскрипт)", - "tog-watchlistunwatchlinks": "Додај означиваче за прекид надгледања/нагледање ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) на надгледане странице са променама (за функционалност пребацивања је потребан јаваскрипт)", + "tog-watchlistreloadautomatically": "Аутоматски поново учитај списак надгледања кад год се филтер промени (потребан JavaScript)", + "tog-watchlistunwatchlinks": "Додаји означиваче за прекид надгледања/нагледање ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) на надгледане странице са променама (за функционалност пребацивања је потребан JavaScript)", "tog-watchlisthideanons": "Сакривај измене анонимних корисника са списка надгледања", "tog-watchlisthidepatrolled": "Сакривај патролиране измене са списка надгледања", "tog-watchlisthidecategorization": "Сакривај категоризацију страница", - "tog-ccmeonemails": "Пошаљи ми копије е-порука које пошаљем другим корисницима", + "tog-ccmeonemails": "Шаљи ми копије е-порука које пошаљем другим корисницима", "tog-diffonly": "Не приказуј садржај странице испод разлика", "tog-showhiddencats": "Приказуј скривене категорије", "tog-norollbackdiff": "Не приказуј разлику након извршеног враћања", - "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање са несачуваним променама", + "tog-useeditwarning": "Упозоравај ме када напуштам страницу за уређивање са несачуваним променама", "tog-prefershttps": "Увек користи безбедну везу док сам пријављен/а.", - "underline-always": "Увек", - "underline-never": "Никад", - "underline-default": "Према теми или прегледачу", + "underline-always": "увек", + "underline-never": "никад", + "underline-default": "према теми или прегледачу", "editfont-style": "Стил фонта у оквиру за уређивање:", - "editfont-monospace": "Сразмерно широк фонт", - "editfont-sansserif": "Бесерифни фонт", - "editfont-serif": "Серифни фонт", + "editfont-monospace": "сразмерно широк фонт", + "editfont-sansserif": "бесерифни фонт", + "editfont-serif": "серифни фонт", "sunday": "недеља", "monday": "понедељак", "tuesday": "уторак", @@ -181,7 +181,7 @@ "newwindow": "(отвара се у новом прозору)", "cancel": "Откажи", "moredotdotdot": "Више…", - "morenotlisted": "Овај списак можда није потпун.", + "morenotlisted": "Ова листа можда није потпуна.", "mypage": "Страница", "mytalk": "Разговор", "anontalk": "Разговор", @@ -330,7 +330,7 @@ "nosuchaction": "Нема такве радње", "nosuchactiontext": "Радња која је наведена у УРЛ-у није важећа.\nМожда сте погрешно откуцали УРЛ или сте следили покварену везу.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.", "nosuchspecialpage": "Нема такве посебне странице", - "nospecialpagetext": "Захтевали сте невалидну посебну страницу.\n\nСписак валидних посебних страница може да се пронађе на „[[Special:SpecialPages|{{int:specialpages}}]]”.", + "nospecialpagetext": "Захтевали сте неважећу посебну страницу.\n\nЛиста важећих посебних страница може се пронађи на „[[Special:SpecialPages|{{int:specialpages}}]]”.", "error": "Грешка", "databaseerror": "Грешка у бази података", "databaseerror-text": "Дошло је до грешке у упиту базе података. \nОво може да указује на грешку у софтверу.", @@ -454,11 +454,11 @@ "userlogin-loggedin": "Већ сте пријављени као {{GENDER:$1|$1}}.\nКористите доњи образац да бисте се пријавили као други корисник.", "userlogin-reauth": "Морате да се поново пријавите да бисте верификовали да сте {{GENDER:$1|$1}}.", "userlogin-createanother": "Отвори још један налог", - "createacct-emailrequired": "Адреса е-поште", - "createacct-emailoptional": "Адреса е-поште (необавезно)", - "createacct-email-ph": "Унесите адресу е-поште", - "createacct-another-email-ph": "Унесите адресу е-поште", - "createaccountmail": "Користи привремену, насумичну лозинку и пошаљи је на наведену адресу е-поште", + "createacct-emailrequired": "Е-адреса", + "createacct-emailoptional": "Е-адреса (опционално)", + "createacct-email-ph": "Унесите е-адресу", + "createacct-another-email-ph": "Унесите е-адресу", + "createaccountmail": "Користи привремену, насумичну лозинку и пошаљи је на наведену е-адресу", "createaccountmail-help": "Може се користити да се некоме отвори налог без сазнања лозинке.", "createacct-realname": "Право име (опционално)", "createacct-reason": "Разлог", @@ -501,40 +501,40 @@ "mailmypassword": "Ресетуј лозинку", "passwordremindertitle": "{{SITENAME}} — привремена лозинка", "passwordremindertext": "Неко са IP адресе $1 је затражио нову лозинку на викију {{SITENAME}} ($4).\nСтворена је привремена лозинка за {{GENDER:$2|корисника|корисницу|корисника}} $2 која гласи $3.\nУколико је ово ваш захтев, сада се пријавите и поставите нову лозинку.\nПривремена лозинка истиче за {{PLURAL:$5|један дан|$5 дана}}.\n\nАко је неко други затражио промену лозинке, или сте се сетили ваше лозинке и не желите да је мењате, занемарите ову поруку.", - "noemail": "{{GENDER:$1|Корисник „$1” није навео|Корисница „$1” није навела|Корисник/ца „$1” није навео/ла}} адресу е-поште.", - "noemailcreate": "Морате да наведете важећу адресу е-поште.", - "passwordsent": "Нова лозинка је послата на адресу е-поште {{GENDER:$1|корисника|кориснице|корисника/це}} $1.\nПоново се пријавите након што је примите.", + "noemail": "{{GENDER:$1|Корисник „$1” није навео|Корисница „$1” није навела|Корисник/ца „$1” није навео/ла}} е-адресу.", + "noemailcreate": "Морате да наведете важећу е-адресу.", + "passwordsent": "Нова лозинка је послата на е-адресу {{GENDER:$1|корисника|кориснице|корисника/це}} $1.\nПоново се пријавите након што је примите.", "blocked-mailpassword": "Уређивање са ваше IP адресе је блокирано. Ради спречавања злоупотребе, забрањена је и функција враћања лозинке са ње.", - "eauthentsent": "Е-порука о потврди је послата на наведену адресу е-поште.\nПре било којих других слања е-порука на налог, мораћете пратити упутства у е-поруци да бисте потврдили да је налог заиста ваш.", + "eauthentsent": "Е-порука са потврдом је послата на наведену е-адресу.\nПре било којих других слања е-порука на налог, мораћете пратити упутства у е-поруци да бисте потврдили да је налог заиста ваш.", "throttled-mailpassword": "Порука за промену лозинке је послата у {{PLURAL:$1|1=последњих сат времена|последња $1 сата|последњих $1 сати}}.\nДа бисмо спречили злоупотребу, подсетник шаљемо само једном у року од {{PLURAL:$1|1=сат времена|$1 сата|$1 сати}}.", "mailerror": "Грешка при слању поруке: $1", "acct_creation_throttle_hit": "Посетиоци овог викија који користе вашу Ај-Пи адресу су отворили {{PLURAL:$1|1=један налог|$1 налога}} у претходних $2, што је највећа дозвољена вредност у овом временском периоду.\nКао резултат тога, ти посетиоци тренутно не могу отварати више налога.", - "emailauthenticated": "Ваша адреса е-поште је потврђена на дан $2 у $3 ч.", - "emailnotauthenticated": "Ваша адреса е-поште још није потврђена.\nНиједна е-порука неће бити послата ни у једном од следећих случајева.", - "noemailprefs": "Наведите адресу е-поште у подешавањима за оспособљавање ових функција.", - "emailconfirmlink": "Потврдите адресу е-поште", - "invalidemailaddress": "Није могуће прихватити адресу е-поште јер је у неважећем формату.\nУнесите добро форматирану адресу или оставите празно поље.", - "cannotchangeemail": "Адресе е-поште налога не могу се променити на овом викију.", + "emailauthenticated": "Ваша е-адреса је потврђена на дан $2 у $3 ч.", + "emailnotauthenticated": "Ваша е-адреса још није потврђена.\nНиједна е-порука неће бити послата ни у једном од следећих случајева.", + "noemailprefs": "Наведите е-адресу у подешавањима за оспособљавање ових функција.", + "emailconfirmlink": "Потврдите е-адресу", + "invalidemailaddress": "Није могуће прихватити е-адресу јер је у неважећем формату.\nУнесите добро форматирану адресу или оставите празно поље.", + "cannotchangeemail": "Е-адресе налога не могу се променити на овом викију.", "emaildisabled": "Ова локација не може да шаље е-поруке.", "accountcreated": "Налог је отворен", "accountcreatedtext": "Кориснички налог [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) је отворен.", "createaccount-title": "Отварање корисничког налога за {{SITENAME}}", - "createaccount-text": "Неко је отворио налог са вашом адресом е-поште на пројекту {{SITENAME}} ($4) под именом „$2” и са лозинком „$3”.\nОдмах требате да се пријавите и промените своју лозинку.\n\nМожете да занемарите ову поруку, ако је овај налог отворен грешком.", + "createaccount-text": "Неко је отворио налог са вашом е-адресом на пројекту {{SITENAME}} ($4) под именом „$2” и са лозинком „$3”.\nОдмах требате да се пријавите и промените своју лозинку.\n\nМожете да занемарите ову поруку, ако је овај налог отворен грешком.", "login-throttled": "Превише пута сте покушали да се пријавите.\nСачекајте $1 пре него што покушате поново.", "login-abort-generic": "Неуспешна пријава – прекинуто", "login-migrated-generic": "Ваш налог је мигриран. Ваше корисничко више не постоји на овом викију.", "loginlanguagelabel": "Језик: $1", "suspicious-userlogout": "Ваш захтев за одјаву је одбијен јер изгледа да га је послао покварени прегледач или кеширани прокси.", - "createacct-another-realname-tip": "Право име је необавезно.\nАко одаберете да га пружите, биће коришћено за приписивање вашег рада.", + "createacct-another-realname-tip": "Право име је опционално.\nАко одаберете да га пружите, биће коришћено за приписивање вашег рада.", "pt-login": "Пријава", "pt-login-button": "Пријави ме", "pt-login-continue-button": "Настави пријављивање", "pt-createaccount": "Отварање налога", "pt-userlogout": "Одјави ме", "php-mail-error-unknown": "Непозната грешка у функцији PHP mail().", - "user-mail-no-addy": "Покушали сте да пошаљете е-поруку без адресе е-поште.", + "user-mail-no-addy": "Покушали сте да пошаљете е-поруку без е-адресе.", "user-mail-no-body": "Покушали сте да пошаљете е-поруку са празним или неразумно кратким садржајем.", - "changepassword": "Промена лозинке", + "changepassword": "Промените лозинку", "resetpass_announce": "Да бисте завршили пријаву, подесите нову лозинку овде.", "resetpass_text": "", "resetpass_header": "Промена лозинке налога", @@ -593,29 +593,29 @@ "passwordreset-emaildisabled": "Функција е-поште је онемогућена на овом викију.", "passwordreset-username": "Корисничко име:", "passwordreset-domain": "Домен:", - "passwordreset-email": "Адреса е-поште:", + "passwordreset-email": "Е-адреса:", "passwordreset-emailtitle": "Детаљи налога на викију {{SITENAME}}", - "passwordreset-emailtext-ip": "Неко (вероватно ви, са IP адресе $1) затражио је ресетовање ваше \nлозинке за пројекат {{SITENAME}} ($4). Следећи кориснички {{PLURAL:$3|налог је повезан|налози су повезани}} \nса овом адресом е-поште:\n\n$2\n\n{{PLURAL:$3|Ова привремена лозинка|Ове привремене лозинке}} истећи ће за {{PLURAL:$5|један дан|$5 дана}}.\nОдмах требате да се пријавите и одаберите нову лозинку. \nАко је неко други направио овај захтев или сте се сетили \nоригиналне лозинке, а не желите да је промените, \nможете да занемарите ову поруку и наставите да \nкористите своју стару лозинку.", + "passwordreset-emailtext-ip": "Неко (вероватно ви, са IP адресе $1) затражио је ресетовање ваше \nлозинке за пројекат {{SITENAME}} ($4). Следећи кориснички {{PLURAL:$3|налог је повезан|налози су повезани}} \nса овом е-адресом:\n\n$2\n\n{{PLURAL:$3|Ова привремена лозинка|Ове привремене лозинке}} истећи ће за {{PLURAL:$5|један дан|$5 дана}}.\nОдмах требате да се пријавите и одаберите нову лозинку. \nАко је неко други направио овај захтев или сте се сетили \nоригиналне лозинке, а не желите да је промените, \nможете да занемарите ову поруку и наставите да \nкористите своју стару лозинку.", "passwordreset-emailtext-user": "Корисник/ца $1 затражио/ла је ресетовање ваше лозинке на пројекту {{SITENAME}} ($4).\nСледећи кориснички {{PLURAL:$3|налог је повезан|налози су повезани}} са овом адресом е-поште:\n\n$2\n\n{{PLURAL:$3|Привремена лозинка истиче|Привремене лозинке истичу}} за {{PLURAL:$5|један дан|$5 дана}}.\nОдмах требате да се пријавите и одаберите нову лозинку. \nАко је неко други направио овај захтев или сте се сетили \nоригиналне лозинке, а не желите да је промените, \nможете да занемарите ову поруку и наставите да \nкористите своју стару лозинку.", "passwordreset-emailelement": "Корисничко име: \n$1\n\nПривремена лозинка: \n$2", - "passwordreset-emailsentemail": "Ако је ова адреса е-поште повезана са вашим налогом, онда ће е-порука о ресетовању лозинке бити послата.", - "passwordreset-emailsentusername": "Ако постоји адреса е-поште повезана са овим корисничким именом, онда ће е-порука о ресетовању лозинке бити послата.", + "passwordreset-emailsentemail": "Ако је ова е-адреса повезана са вашим налогом, онда ће е-порука о ресетовању лозинке бити послата.", + "passwordreset-emailsentusername": "Ако постоји е-адреса повезана са овим корисничким именом, онда ће е-порука о ресетовању лозинке бити послата.", "passwordreset-nocaller": "Позивалац се мора навести", "passwordreset-nosuchcaller": "Позивалац не постоји: $1", "passwordreset-ignored": "Ресетовање лозинке није успело. Можда послужилац није конфигурисан?", - "passwordreset-invalidemail": "Неважећа адреса е-поште", - "passwordreset-nodata": "Корисничко име и адреса е-поште нису наведени", - "changeemail": "Промена или уклањање адресе е-поште", - "changeemail-header": "Довршите овај образац да би сте променили адресу е-поште. Ако бисте желели да уклоните повезаност било које адресе е-поште са вашег налога, оставите празно поље за нову адресу е-поште када шаљете образац.", + "passwordreset-invalidemail": "Неважећа е-адреса", + "passwordreset-nodata": "Корисничко име и е-адреса нису наведени", + "changeemail": "Промена или уклањање е-адресе", + "changeemail-header": "Довршите овај образац да би сте променили е-адресу. Ако бисте желели да уклоните повезаност било које е-адресе са вашег налога, оставите празно поље за нову е-адресу када шаљете образац.", "changeemail-no-info": "Морате бити пријављени да бисте приступили овој страници.", - "changeemail-oldemail": "Тренутна адреса е-поште:", - "changeemail-newemail": "Нова адреса е-поште:", - "changeemail-newemail-help": "Ово поље треба да оставите празно ако желите да уклоните адресу е-поште. Нећете бити у могућности да ресетујете заборављену лозинку и нећете примати е-поруке са овог викија ако је адреса е-поште уклоњена.", + "changeemail-oldemail": "Тренутна е-адреса:", + "changeemail-newemail": "Нова е-адреса:", + "changeemail-newemail-help": "Ово поље треба да оставите празно ако желите да уклоните е-адресу. Нећете бити у могућности да ресетујете заборављену лозинку и нећете примати е-поруке са овог викија ако је е-адреса уклоњена.", "changeemail-none": "(ништа)", "changeemail-password": "Ваша лозинка за пројекат {{SITENAME}}:", "changeemail-submit": "Промени е-пошту", "changeemail-throttled": "Превише пута сте покушали да се пријавите.\nМолимо вас да сачекате $1 пре него што покушате поново.", - "changeemail-nochange": "Унесите другу адресу е-поште.", + "changeemail-nochange": "Унесите другу е-адресу.", "resettokens": "Ресетовање токена", "resettokens-text": "Овде можете да ресетујете токене који омогућавају приступ одређеним приватним подацима повезаним са вашим налогом.\n\nТребали бисте то урадити ако их случајно поделите са неким или ако је ваш налог угрожен.", "resettokens-no-tokens": "Нема жетона за ресетовање.", @@ -670,11 +670,11 @@ "blockedtitle": "Корисник је блокиран", "blocked-email-user": "Вашем корисничком имену је блокирано слање е-порука. Још увек можете да уређујете друге странице на овом викију. Можете да видите потпуне детаље блокаде на [[Special:MyContributions|доприносима налога]].\n\nБлокаду је извршио/ла $1.\n\nНаведен је следећи разлог: $2.\n\n* Почетак блокаде: $8\n* Истек блокаде: $6\n* Намењена кориснику/ци или IP адреси: $7\n* ID блокаде #$5", "blockedtext-partial": "Вашем корисничком имену или IP адреси је блокирано прављење промена на овој страници. Још увек можете да уређујете друге странице на овом викију. Можете да видите потпуне детаље блокаде на [[Special:MyContributions|доприносима налога]].\n\nБлокаду је извршио/ла $1.\n\nНаведен је следећи разлог: $2.\n\n* Почетак блокаде: $8\n* Истек блокаде: $6\n* Намењена кориснику/ци или IP адреси: $7\n* ID блокаде #$5", - "blockedtext": "Ваше корисничко име или IP адреса је блокирана.\n\nБлокирање је {{GENDER:$4|извршио|извршила}} $1.\nРазлог је $2.\n\n* Почетак блокирања: $8\n* Истек блокирања: $6\n* Блокирани: $7\n\nМожете да се обратите {{GENDER:$4|кориснику|корисници}} $1 или [[{{MediaWiki:Grouppage-sysop}}|администратору]] ради дискусије о блокирању.\nНе можете да користите могућност „{{int:emailuser}}” осим ако сте унели валидну имејл адресу у својим [[Special:Preferences|подешавањима]] налога и нисте блокирани од коришћења исте.\nВаша тренутна IP адреса је $3, а ID блокирања #$5.\nНаведите све информације одозго при стварању било каквих упита.", - "autoblockedtext": "Ваша IP адреса је аутоматски блокирана јер ју је користио други корисник, кога је {{GENDER:$4|блокирао|блокирала|блокирао/ла}} $1.\nРазлог:\n\n:$2\n\n* Почетак блокаде: $8\n* Крај блокаде: $6\n* Име корисника: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу|корисника/цу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте расправљали о блокади.\n\nЗапамтите да не можете да користите функцију „{{int:emailuser}}“ осим ако сте навели важећу адресу е-поште у [[Special:Preferences|подешавањима]].\n\nВаша тренутна IP адреса је $3, а ID блокаде $5.\nУкључите све горње детаље при прављењу било каквих упита.", + "blockedtext": "Ваше корисничко име или IP адреса је блокирана.\n\nБлокирање је {{GENDER:$4|извршио|извршила}} $1.\nРазлог је $2.\n\n* Почетак блокирања: $8\n* Истек блокирања: $6\n* Блокирани: $7\n\nМожете да се обратите {{GENDER:$4|кориснику|корисници}} $1 или [[{{MediaWiki:Grouppage-sysop}}|администратору]] ради дискусије о блокирању.\nНе можете да користите функцију „{{int:emailuser}}” осим ако сте унели важећу е-адресу у својим [[Special:Preferences|подешавањима]] налога и нисте блокирани од коришћења исте.\nВаша тренутна IP адреса је $3, а ID блокирања #$5.\nНаведите све информације одозго при стварању било каквих упита.", + "autoblockedtext": "Ваша IP адреса је аутоматски блокирана јер ју је користио други корисник, кога је {{GENDER:$4|блокирао|блокирала|блокирао/ла}} $1.\nРазлог:\n\n:$2\n\n* Почетак блокаде: $8\n* Крај блокаде: $6\n* Име корисника: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу|корисника/цу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте расправљали о блокади.\n\nЗапамтите да не можете да користите функцију „{{int:emailuser}}“ осим ако сте навели важећу е-адресу у [[Special:Preferences|подешавањима]].\n\nВаша тренутна IP адреса је $3, а ID блокаде $5.\nУкључите све горње детаље при прављењу било каквих упита.", "blockednoreason": "разлог није наведен", "whitelistedittext": "$1 да бисте уређивали странице.", - "confirmedittext": "Морате да потврдите адресу е-поште пре уређивања страница.\nПоставите и проверите ваљаност адресе преко [[Special:Preferences|подешавања]].", + "confirmedittext": "Морате да потврдите е-адресу пре уређивања страница.\nПоставите и проверите ваљаност адресе преко [[Special:Preferences|подешавања]].", "nosuchsectiontitle": "Није могуће пронаћи одељак", "nosuchsectiontext": "Покушали сте да уредите одељак који не постоји.\nМожда је премештен или избрисан док сте прегледали страницу.", "loginreqtitle": "Потребна је пријава", @@ -905,7 +905,7 @@ "revdelete-edit-reasonlist": "Уреди разлоге за брисање", "revdelete-offender": "Аутор измене:", "suppressionlog": "Дневник сакривања", - "suppressionlogtext": "Испод се налази списак брисања и блокирања који укључује садржај сакривен од администратора. Погледајте [[Special:BlockList|списак блокирања]] за списак тренутних операција забрана и блокирања.", + "suppressionlogtext": "Испод се налази листа брисања и блокирања који укључује садржај сакривен од администратора. Погледајте [[Special:BlockList|листу блокирања]] за списак тренутних операција забрана и блокирања.", "mergehistory": "Обједињавање историја странице", "mergehistory-header": "Ова страница вам допушта да обједините историју измена неке изворне странице у новију страницу.\nУверите се да ће ова промена оставити непромењен садржај историје странице.", "mergehistory-box": "Обједини измене двеју страница:", @@ -937,7 +937,7 @@ "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6", "mergelog": "Дневник обједињавања", "revertmerge": "растави", - "mergelogpagetext": "Испод је списак најновијих обједињавања историја једне странице у другу.", + "mergelogpagetext": "Испод се налази листа најновијих обједињавања историја једне странице у другу.", "history-title": "Историја измена странице „$1”", "difference-title": "Разлика између измена на страници „$1”", "difference-title-multipage": "Разлика између страница „$1“ и „$2“", @@ -1020,20 +1020,20 @@ "prefs-rc": "Скорашње измене", "prefs-watchlist": "Списак надгледања", "prefs-editwatchlist": "Уређивање списка надгледања", - "prefs-editwatchlist-label": "Уреди уносе на списку надгледања:", - "prefs-editwatchlist-edit": "Приказ и уклањање наслова са списка надгледања", - "prefs-editwatchlist-raw": "Уређивање необрађеног списка надгледања", - "prefs-editwatchlist-clear": "Чишћење списка надгледања", + "prefs-editwatchlist-label": "Уредите уносе на списку надгледања:", + "prefs-editwatchlist-edit": "Погледајте и уклоните наслове са списка надгледања", + "prefs-editwatchlist-raw": "Уредите необрађен списак надгледања", + "prefs-editwatchlist-clear": "Очистите списак надгледања", "prefs-watchlist-days": "Број дана у списку надгледања:", "prefs-watchlist-days-max": "Највише $1 {{PLURAL:$1|дан|дана|дана}}", "prefs-watchlist-edits": "Највећи број промена приказаних на списку надгледања:", "prefs-watchlist-edits-max": "Највећи број: 1000", "prefs-watchlist-token": "Токен списка надгледања:", - "prefs-watchlist-managetokens": "Управљање токенима", + "prefs-watchlist-managetokens": "Управљај токенима", "prefs-misc": "Разно", "prefs-resetpass": "Промена лозинке", - "prefs-changeemail": "Промена или уклањање адресе е-поште", - "prefs-setemail": "Постављање адресе е-поште", + "prefs-changeemail": "Промените или уклоните е-адресу", + "prefs-setemail": "Постављање е-адресе", "prefs-email": "Опције е-поште", "prefs-rendering": "Изглед", "saveprefs": "Сачувај", @@ -1042,7 +1042,7 @@ "searchresultshead": "Претрага", "stub-threshold": "Праг за форматирање веза као клице ($1):", "stub-threshold-sample-link": "пример", - "stub-threshold-disabled": "Онемогућено", + "stub-threshold-disabled": "онемогућено", "recentchangesdays": "Број дана у скорашњим изменама:", "recentchangesdays-max": "Највише $1 {{PLURAL:$1|дан|дана}}", "recentchangescount": "Подразумевани број измена за приказ у скорашњим изменама, историјама страница и дневницима:", @@ -1053,11 +1053,11 @@ "savedrights": "Корисничке групе {{GENDER:$1|корисника|кориснице}} $1 су сачуване.", "timezonelegend": "Временска зона:", "localtime": "Локално време:", - "timezoneuseserverdefault": "Користи подразумеване вредности викија ($1)", - "timezoneuseoffset": "Друго (наведите одступање испод)", + "timezoneuseserverdefault": "користи подразумеване вредности викија ($1)", + "timezoneuseoffset": "друго (наведите одступање испод)", "timezone-useoffset-placeholder": "Примери вредности: „-07:00” или „01:00”", "servertime": "Време на серверу:", - "guesstimezone": "Попуни из прегледача", + "guesstimezone": "попуни из прегледача", "timezoneregion-africa": "Африка", "timezoneregion-america": "Америка", "timezoneregion-antarctica": "Антарктик", @@ -1068,17 +1068,17 @@ "timezoneregion-europe": "Европа", "timezoneregion-indian": "Индијски океан", "timezoneregion-pacific": "Тихи океан", - "allowemail": "Дозволи другим корисницима да ми шаљу е-поруке", - "email-allow-new-users-label": "Дозволи примање е-порука од новајлија", - "email-blacklist-label": "Забрани овим корисницима да ми шаљу е-поруке:", + "allowemail": "Дозвољавај другим корисницима да ми шаљу е-поруке", + "email-allow-new-users-label": "Дозвољавај е-поруке од потпуно нових корисника", + "email-blacklist-label": "Забрањуј следећим корисницима да ми шаљу е-поруке:", "prefs-searchoptions": "Претрага", "prefs-namespaces": "Именски простори", "default": "подразумевано", "prefs-files": "Датотеке", - "prefs-custom-css": "прилагођени Це-Ес-Ес", + "prefs-custom-css": "прилагођени CSS", "prefs-custom-json": "Прилагођени ЈСОН", - "prefs-custom-js": "прилагођени јаваскрипт", - "prefs-common-config": "Дељени Це-Ес-Ес/ЈСОН/јаваскрипт за све теме:", + "prefs-custom-js": "прилагођени JavaScript", + "prefs-common-config": "Дељени CSS/JSON/JavaScript за све теме:", "prefs-reset-intro": "Можете користити ову страницу да поново поставите своја подешавања на подразумеване вредности сајта.\nОво се не може опозвати.", "prefs-emailconfirm-label": "Потврда е-поште:", "youremail": "Е-пошта:", @@ -1097,15 +1097,15 @@ "badsig": "Неважећи необрађени потпис.\nПроверите HTML тагове.", "badsiglength": "Ваш потпис је предугачак.\nНе сме бити дужи од $1 {{PLURAL:$1|знака|знака|знакова}}.", "yourgender": "Како желите да се представите?", - "gender-unknown": "Кад вас спомиње, софтвер ће користити родно неутралне речи кад год је то могуће", + "gender-unknown": "Кад вас помиње, софтвер ће користити родно неутралне речи кад год је то могуће", "gender-male": "Он уређује вики странице", "gender-female": "Она уређује вики странице", - "prefs-help-gender": "Постављање овог подешавања је необавезно.\nСофтвер користи дату вредност да би вам се обратио и споменуо вас другима користећи одговарајући граматички род.\nОва информација ће бити јавна.", + "prefs-help-gender": "Постављање овог подешавања је опционално.\nСофтвер користи дату вредност да би вам се обратио и поменуо вас другима користећи одговарајући граматички род.\nОва информација ће бити јавна.", "email": "Е-пошта", - "prefs-help-realname": "Право име је необавезно.\nАко је пружено, биће коришћено за приписивање вашег рада.", - "prefs-help-email": "Адреса е-поште је необавезна, али је потребна за ресетовање лозинке, ако је заборавите.", - "prefs-help-email-others": "Такође можете оабрати да допустите другима да вас контактирају преко е-поште путем везе на вашој корисничкој страници или страници за разговор.\nВаша адреса е-поште неће бити приказана другим корисницима који вас контактирају.", - "prefs-help-email-required": "Потребна је адреса е-поште.", + "prefs-help-realname": "Право име је опционално.\nАко је пружено, биће коришћено за приписивање вашег рада.", + "prefs-help-email": "Е-адреса је опционална, али је потребна за ресетовање лозинке, ако је заборавите.", + "prefs-help-email-others": "Такође можете одабрати да допустите другима да вас контактирају путем е-поште преко везе на вашој корисничкој страници или страници за разговор.\nВаша е-адреса неће бити приказана другим корисницима који вас контактирају.", + "prefs-help-email-required": "Потребна је е-адреса.", "prefs-info": "Основне информације", "prefs-i18n": "Интернационализација", "prefs-signature": "Потпис", @@ -1235,8 +1235,8 @@ "right-editmyuserjs": "уређивање сопствених јаваскрипт датотека", "right-viewmywatchlist": "преглед сопственог списка надгледања", "right-editmywatchlist": "уређивање сопственог списка надгледања; неке предузете радње ће свеједно додати странице на списак и без овог права", - "right-viewmyprivateinfo": "преглед сопствених приватних података (нпр. адресе е-поште, право име)", - "right-editmyprivateinfo": "уређивање сопствених приватних података (нпр. адресе е-поште, правог имена)", + "right-viewmyprivateinfo": "преглед сопствених приватних података (нпр. е-адресе, правог имена)", + "right-editmyprivateinfo": "уређивање сопствених приватних података (нпр. е-адресе, правог имена)", "right-editmyoptions": "уређивање сопствених подешавања", "right-rollback": "брзо враћање измена последњег корисника који је мењао одређену страницу", "right-markbotedits": "означавање враћених измена као измене бота", @@ -1569,7 +1569,7 @@ "upload-preferred": "Препоручени {{PLURAL:$2|тип|типови}} датотека: $1.", "upload-prohibited": "Забрањени {{PLURAL:$2|тип|типови}} датотека: $1.", "uploadlogpage": "Дневник отпремања", - "uploadlogpagetext": "Испод је списак недавних отпремања.\nПогледајте [[Special:NewFiles|галерију нових датотека]] за лепши преглед.", + "uploadlogpagetext": "Испод се налази листа најновијих отпремања.\nПогледајте [[Special:NewFiles|галерију нових датотека]] за визуелнији преглед.", "filename": "Назив датотеке", "filedesc": "Опис измене", "fileuploadsummary": "Опис измене:", @@ -1653,7 +1653,7 @@ "upload-file-error-text": "Дошло је до унутрашње грешке при отварању привремене датотеке на серверу.\nКонтактирајте [[Special:ListUsers/sysop|администратора]].", "upload-misc-error": "Непозната грешка при слању датотеке", "upload-misc-error-text": "Дошло је до непознате грешке при отпремању датотеке.\nПроверите да ли је УРЛ важећи и доступан, па покушајте поново.\nАко се проблем буде поново јавио, обратите се [[Special:ListUsers/sysop|администратору]].", - "upload-too-many-redirects": "Адреса садржи превише преусмерења", + "upload-too-many-redirects": "URL садржи превише преусмерења", "upload-http-error": "Дошло је до HTTP грешке: $1", "upload-copy-upload-invalid-domain": "Примерци отпремања нису доступни на овом домену.", "upload-dialog-disabled": "Отпремања датотека коришћењем овог дијалога су онемогућена на овом викију.", @@ -1722,7 +1722,7 @@ "uploadstash-nofiles": "Немате сакривене датотеке.", "uploadstash-badtoken": "Извршавање ове радње није успело, разлог томе може бити истек времена за уређивање. Покушајте поново.", "uploadstash-errclear": "Брисање датотека није успело.", - "uploadstash-refresh": "Освежи списак датотека", + "uploadstash-refresh": "Освежи листу датотека", "uploadstash-thumbnail": "прикажи сличицу", "uploadstash-exception": "Не могу сачувати датотеку у складиште ($1): „$2“.", "uploadstash-bad-path": "Путања не постоји.", @@ -1755,7 +1755,7 @@ "img-auth-public": "Сврха img_auth.php је да прослеђује датотеке из приватних викија.\nОвај вики је постављен као јавни.\nРади сигурности, img_auth.php је онемогућен.", "img-auth-noread": "Корисник нема приступ за читање „$1“.", "http-invalid-url": "Неважећи УРЛ: $1", - "http-invalid-scheme": "Адресе са шемом „$1“ нису подржане.", + "http-invalid-scheme": "URL-ови са шемом „$1” нису подржани.", "http-request-error": "HTTP захтев није прошао због непознате грешке.", "http-read-error": "HTTP грешка при читању.", "http-timed-out": "Захтев HTTP је истекао.", @@ -1778,7 +1778,7 @@ "listfiles_search_for": "Претражи име медија:", "listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.", "imgfile": "датотека", - "listfiles": "Списак датотека", + "listfiles": "Листа датотека", "listfiles_thumb": "Сличица", "listfiles_date": "Датум", "listfiles_name": "Назив", @@ -1807,7 +1807,7 @@ "filehist-comment": "Коментар", "imagelinks": "Употреба датотеке", "linkstoimage": "{{PLURAL:$1|Следећа страница користи|$1 следеће странице користе|$1 следећих страница користи}} ову датотеку:", - "linkstoimage-more": "Више од $1 {{PLURAL:$1|страница користи|странице користе|страница користи}} ову датотеку.\nСледећи списак приказује {{PLURAL:$1|прву страницу која користи|прве $1 странице које користе|првих $1 страница које користе}} само ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].", + "linkstoimage-more": "Више од $1 {{PLURAL:$1|страница користи|странице користе|страница користи}} ову датотеку.\nСледећа листа приказује {{PLURAL:$1|прву страницу која користи|прве $1 странице које користе|првих $1 страница које користе}} само ову датотеку.\nДоступна је и [[Special:WhatLinksHere/$2|потпуна листа]].", "nolinkstoimage": "Нема страница које користе ову датотеку.", "morelinkstoimage": "Погледајте [[Special:WhatLinksHere/$1|више веза]] до ове датотеке.", "linkstoimage-redirect": "$1 (преусмерење датотеке) $2", @@ -1855,9 +1855,9 @@ "mimetype": "МИМЕ врста:", "download": "преузми", "unwatchedpages": "Ненадгледане странице", - "listredirects": "Списак преусмерења", - "listduplicatedfiles": "Списак датотека са дупликатима", - "listduplicatedfiles-summary": "Ово је списак датотека које су дупликат неких других датотека. Само локалне датотеке су приказане.", + "listredirects": "Листа преусмерења", + "listduplicatedfiles": "Листа датотека са дупликатима", + "listduplicatedfiles-summary": "Ово је листа датотека чија је најновија верзија дупликат неких других датотека. Само локалне датотеке су приказане.", "listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|један дупликат|$2 дупликата}}]].", "unusedtemplates": "Некоришћени шаблони", "unusedtemplatestext": "Ова страница наводи све странице у именском простору {{ns:template}} које нису укључене ни на једној другој страници.\nПре брисања проверите да ли друге странице воде до тих шаблона.", @@ -1929,7 +1929,7 @@ "unusedimages": "Некоришћене датотеке", "wantedcategories": "Тражене категорије", "wantedpages": "Тражене странице", - "wantedpages-summary": "Списак непостојећих страница са највише веза до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].", + "wantedpages-summary": "Листа непостојећих страница са највише веза на њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења, погледајте [[{{#special:BrokenRedirects}}|листу покварених преусмерења]].", "wantedpages-badtitle": "Невалидан наслов у скупу резултата: $1", "wantedfiles": "Тражене датотеке", "wantedfiletext-cat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити поништене са списка. Поред тога, странице које садрже непостојеће датотеке се налазе [[:$1|овде]].", @@ -1954,7 +1954,7 @@ "protectedpages": "Заштићене странице", "protectedpages-filters": "Филтери:", "protectedpages-indef": "Само неограничене заштите", - "protectedpages-summary": "На овој страници се налази списак постојећих страница које су тренутно заштићене. За списак наслова који су заштићени од прављења, погледајте [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].", + "protectedpages-summary": "Ова страница наводи постојеће странице које су тренутно заштићене. За листу наслова који су заштићени од прављења, погледајте [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].", "protectedpages-cascade": "Само преносиве заштите", "protectedpages-noredirect": "Сакриј преусмерења", "protectedpagesempty": "Нема заштићених страница с овим параметрима.", @@ -1968,10 +1968,10 @@ "protectedpages-unknown-timestamp": "нема", "protectedpages-unknown-performer": "нема", "protectedtitles": "Заштићени наслови", - "protectedtitles-summary": "На овој страници се налазе наслови који су тренутно заштићени од прављења. За списак постојећих страница које су заштићене, погледајте [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].", + "protectedtitles-summary": "Ова страница наводи наслове који су тренутно заштићени од прављења. За листу постојећих страница које су заштићене, погледајте [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].", "protectedtitlesempty": "Нема заштићених наслова с овим параметрима.", "protectedtitles-submit": "Прикажи наслове", - "listusers": "Списак корисника", + "listusers": "Листа корисника", "listusers-editsonly": "Прикажи само кориснике који су уређивали", "listusers-temporarygroupsonly": "Прикажи само кориснике у привременим корисничким групама", "listusers-creationsort": "Сортирај по датуму прављења", @@ -2038,7 +2038,7 @@ "booksources-search-legend": "Претражи штампане изворе", "booksources-isbn": "ISBN:", "booksources-search": "Претражи", - "booksources-text": "Испод се налази списак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:", + "booksources-text": "Испод се налази листа веза на друге сајтове који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:", "booksources-invalid-isbn": "Наведени ISBN број није валидан. Проверите да није дошло до грешке при копирању из првобитног извора.", "magiclink-tracking-rfc": "Странице са чаробним RFC везама", "magiclink-tracking-pmid": "Странице са чаробним PMID везама", @@ -2093,8 +2093,8 @@ "listusers-submit": "Прикажи", "listusers-noresult": "Корисник није пронађен.", "listusers-blocked": "({{GENDER:$1|блокиран|блокирана|блокиран}})", - "activeusers": "Списак активних корисника", - "activeusers-intro": "Ово је списак корисника који су били активни {{PLURAL:$1|1=претходни дан|у последња $1 дана|у последњих $1 дана}}.", + "activeusers": "Листа активних корисника", + "activeusers-intro": "Ово је листа корисника који су били активни {{PLURAL:$1|1=претходни дан|у последња $1 дана|у последњих $1 дана}}.", "activeusers-count": "$1 {{PLURAL:$1|радња|радње|радњи}} {{PLURAL:$3|претходни дан|у последња $3 дана|у последњих $3 дана}}", "activeusers-from": "Прикажи кориснике почев од:", "activeusers-groups": "Прикажи кориснике који су чланови група:", @@ -2102,7 +2102,7 @@ "activeusers-noresult": "Корисник није пронађен.", "activeusers-submit": "Прикажи активне кориснике", "listgrouprights": "Права корисничких група", - "listgrouprights-summary": "Следи списак корисничких група на овом викију, заједно с правима приступа.\nПогледајте [[{{MediaWiki:Listgrouprights-helppage}}|више детаља]] о појединачним правима.", + "listgrouprights-summary": "Следи листа корисничких група дефинисаних на овом викију, заједно са повезаним правима приступа.\nМожда постоји [[{{MediaWiki:Listgrouprights-helppage}}|више информација]] о појединачним правима.", "listgrouprights-key": "Легенда:\n* Додељено право\n* Укинуто право", "listgrouprights-group": "Група", "listgrouprights-rights": "Права", @@ -2126,7 +2126,7 @@ "listgrants-rights": "Права", "listgrants-grant-display": "$1 ($2)", "trackingcategories": "Категорије за праћење", - "trackingcategories-summary": "Ова посебна страница је списак категорија које су део Медијавикија, оне се аутоматски ажурирају и њихови називи се могу мењати уређивањем системских порука у именском простору {{ns:8}}.", + "trackingcategories-summary": "Ова страница наводи категорије за праћење које аутоматски попуњава софтвер Медијавики. Њихова имена се могу променити изменом одговарајућих системских порука у именском простору {{ns:8}}.", "trackingcategories-msg": "Категорије за праћење", "trackingcategories-name": "Име поруке", "trackingcategories-desc": "Које странице се налазе у категорији", @@ -2138,16 +2138,16 @@ "trackingcategories-nodesc": "Опис није доступан.", "trackingcategories-disabled": "Категорија је онемогућена", "mailnologin": "Нема адресе за слање", - "mailnologintext": "Морате да се [[Special:UserLogin|пријавите]] и имате важећи адресу е-поште у [[Special:Preferences|подешавањима]] да бисте слали е-поруке другим корисницима.", + "mailnologintext": "Морате да се [[Special:UserLogin|пријавите]] и имате важећи е-адресу у [[Special:Preferences|подешавањима]] да бисте слали е-поруке другим корисницима.", "emailuser": "Пошаљи е-поруку овом кориснику/ци", "emailuser-title-target": "Слање е-поруке {{GENDER:$1|кориснику|корисници|кориснику/ци}}", - "emailuser-title-notarget": "Слање имејла кориснику", - "emailpagetext": "Можете да користите доњи образац да пошаљете имејл {{GENDER:$1|овом кориснику|овој корисници}}.\nИмејл који сте унели у вашим [[Special:Preferences|подешавањима]] ће се приказати у пољу „Од“, тако да ће прималац моћи да вам одговори директно.", + "emailuser-title-notarget": "Слање е-поруке кориснику", + "emailpagetext": "Можете да користите доњи образац да пошаљете е-поруку {{GENDER:$1|овом кориснику|овој корисници}}.\nЕ-адреса који сте унели у вашим [[Special:Preferences|подешавањима]] ће се приказати у пољу „Од”, тако да ће прималац моћи да вам одговори директно.", "defemailsubject": "{{SITENAME}} — е-порука од {{GENDER:$1|корисника|кориснице|корисника/це}} „$1”", "usermaildisabled": "Корисничка е-пошта је онемогућена", "usermaildisabledtext": "Не можете да шаљете е-поруке другим корисницима на овом викију", - "noemailtitle": "Нема адресе е-поште", - "noemailtext": "Овај корисник није навео важећу адресу е-поште.", + "noemailtitle": "Нема е-адресе", + "noemailtext": "Овај корисник није навео важећу е-адресу.", "nowikiemailtext": "Овај корисник је одабрао да не прима е-поруке од других корисника.", "emailnotarget": "Непостојеће или наважеће корисничко име примаоца.", "emailtarget": "Унос корисничког имена примаоца", @@ -2163,7 +2163,7 @@ "emailccsubject": "Копија поруке кориснику/ци $1: $2", "emailsent": "Е-порука је послата", "emailsenttext": "Ваша е-порука је послата.", - "emailuserfooter": "Ову е-поруку је {{GENDER:$1|послао|послала|послао/ла}} $1 {{GENDER:$2|кориснику|корисници|кориснику/ци}} $2 помоћу опције „{{int:emailuser}}” на пројекту {{SITENAME}}. Ако одговорите на ову е-поруку, {{GENDER:$2|ваша}} е-порука биће непосредно прослеђена ка {{GENDER:$1|оригиналном пошиљаоцу}}, чиме ћете {{GENDER:$2|му|јој}} открити {{GENDER:$2|адресу е-поште}}.", + "emailuserfooter": "Ову е-поруку је {{GENDER:$1|послао|послала|послао/ла}} $1 {{GENDER:$2|кориснику|корисници|кориснику/ци}} $2 помоћу опције „{{int:emailuser}}” на пројекту {{SITENAME}}. Ако одговорите на ову е-поруку, {{GENDER:$2|ваша}} е-порука биће непосредно прослеђена ка {{GENDER:$1|оригиналном пошиљаоцу}}, чиме ћете {{GENDER:$2|му|јој}} открити {{GENDER:$2|е-адресу}}.", "usermessage-summary": "Слање системске поруке.", "usermessage-editor": "Уређивач системских порука", "usermessage-template": "MediaWiki:UserMessage", @@ -2176,7 +2176,7 @@ "addwatch": "Додавање на списак надгледања", "addedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је додата на ваш [[Special:Watchlist|списак надгледања]].", "addedwatchtext-talk": "Страница „[[:$1]]” и њена придружена страница је додата на ваш [[Special:Watchlist|списак надгледања]]", - "addedwatchtext-short": "Страница „$1“ је додата на ваш списак надгледања.", + "addedwatchtext-short": "Страница „$1” је додата на списак надгледања.", "removewatch": "Уклони са списка надгледања", "removedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је уклоњена са вашег [[Special:Watchlist|списка надгледања]].", "removedwatchtext-talk": "\"[[:$1]]\" и његове повезане странице су уклоњене са вашег [[Special:Watchlist|списка надгледања]].", @@ -2221,7 +2221,7 @@ "enotif_lastvisited": "За све промене од последње посете, погледајте $1.", "enotif_lastdiff": "Да бисте видели ову промену, погледајте $1.", "enotif_anon_editor": "анониман корисник $1", - "enotif_body": "Поштовани $WATCHINGUSERNAME,\n \t\n$PAGEINTRO $NEWPAGE\n\nРезиме уређивача: $PAGESUMMARY $PAGEMINOREDIT\n\nКонтакт:\nмејл: $PAGEEDITOR_EMAIL\nвики: $PAGEEDITOR_WIKI\n\nНеће бити других обавештења у случају даљих измена уколико не посетите ову страницу када сте пријављени.\nМожете и да поништите подешавања обавештења за све странице у вашем списку надгледања.\n\nСрдачан поздрав, {{SITENAME}}\n\n--\nДа бисте променили подешавања имејл обавештења, посетите\n{{canonicalurl:{{#special:Preferences}}}}\n\nДа бисте променили подешавања списка надгледања, посетите\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nДа бисте уклонили ову страницу са списка надгледања, посетите\n$UNWATCHURL\n\nПодршка и даља помоћ:\n$HELPPAGE", + "enotif_body": "Поштовани $WATCHINGUSERNAME,\n \t\n$PAGEINTRO $NEWPAGE\n\nРезиме уређивача: $PAGESUMMARY $PAGEMINOREDIT\n\nКонтакт:\nпошта: $PAGEEDITOR_EMAIL\nвики: $PAGEEDITOR_WIKI\n\nНеће бити других обавештења у случају даљих измена уколико не посетите ову страницу када сте пријављени.\nМожете и да поништите подешавања обавештења за све странице у вашем списку надгледања.\n\nСрдачан поздрав, {{SITENAME}}\n\n--\nДа бисте променили подешавања обавештавања путем е-поште, посетите\n{{canonicalurl:{{#special:Preferences}}}}\n\nДа бисте променили подешавања списка надгледања, посетите\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nДа бисте уклонили ову страницу са списка надгледања, посетите\n$UNWATCHURL\n\nПодршка и даља помоћ:\n$HELPPAGE", "enotif_minoredit": "Ово је мања измена", "created": "направљена", "changed": "измењена", @@ -2239,10 +2239,10 @@ "actionfailed": "Радња није успела", "deletedtext": "Страница „$1“ је избрисана.\nПогледајте $2 за запис недавних брисања.", "dellogpage": "Дневник брисања", - "dellogpagetext": "Испод је списак недавних брисања.", + "dellogpagetext": "Испод се налази листа најновијих брисања.", "deletionlog": "дневник брисања", "log-name-create": "Дневник прављења страница", - "log-description-create": "Испод је списак недавних прављења страница.", + "log-description-create": "Испод се налази листа најновијих прављења страница.", "logentry-create-create": "$1 је {{GENDER:$2|направио|направила}} страницу $3", "reverted": "Враћено на ранију измену", "deletecomment": "Разлог:", @@ -2256,6 +2256,7 @@ "deleting-backlinks-warning": "Упозорење: бришете страницу која је укључена у [[Special:WhatLinksHere/{{FULLPAGENAME}}|друге странице]] или друге странице воде на њу.", "deleting-subpages-warning": "Упозорење: Страница коју желите избрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].", "rollback": "Врати измене", + "rollback-confirmation-confirm": "Потврдите:", "rollbacklink": "врати", "rollbacklinkcount": "врати $1 {{PLURAL:$1|измену|измене|измена}}", "rollbacklinkcount-morethan": "врати више од $1 {{PLURAL:$1|измене|измене|измена}}", @@ -2289,7 +2290,7 @@ "logentry-contentmodel-change-revertlink": "врати", "logentry-contentmodel-change-revert": "врати", "protectlogpage": "Дневник заштите", - "protectlogtext": "Испод је списак заштићених страница.\nПогледајте [[Special:ProtectedPages|списак заштићених страница]] за више детаља.", + "protectlogtext": "Испод се налази листа промена заштите страница.\nПогледајте [[Special:ProtectedPages|листу заштићених страница]] за тренутно оперативне заштите страница.", "protectedarticle": "је {{GENDER:|заштитио|заштитила}} страницу „[[$1]]“", "modifiedarticleprotection": "је {{GENDER:|променио|променила}} ниво заштите странице „[[$1]]“", "unprotectedarticle": "је скинуо заштиту са странице „[[$1]]“", @@ -2492,10 +2493,10 @@ "blocklist": "Блокирани корисници", "autoblocklist": "Аутоблокови", "autoblocklist-submit": "Претражи", - "autoblocklist-legend": "Списак аутоблокирања", + "autoblocklist-legend": "Листа аутоблокирања", "autoblocklist-localblocks": "{{PLURAL:$1|Локални аутоблок|Локални аутоблокови}}", "autoblocklist-total-autoblocks": "Укупно аутоблокова: $1", - "autoblocklist-empty": "Списак аутоблокирања је празан.", + "autoblocklist-empty": "Листа аутоблокирања је празна.", "autoblocklist-otherblocks": "{{PLURAL:$1|Други аутоблок|Други аутоблокови}}", "ipblocklist": "Блокирани корисници", "ipblocklist-legend": "Проналажење блокираног корисника", @@ -2523,7 +2524,7 @@ "blocklist-editing-sitewide": "уређивање (на целом сајту)", "blocklist-editing-page": "странице", "blocklist-editing-ns": "именски простори", - "ipblocklist-empty": "Списак блокирања је празан.", + "ipblocklist-empty": "Листа блокирања је празна.", "ipblocklist-no-results": "Тражена IP адреса или корисничко име није блокирано.", "blocklink": "блокирај", "unblocklink": "деблокирај", @@ -2536,7 +2537,7 @@ "blocklog-showsuppresslog": "{{GENDER:$1|Овај корисник је раније блокиран и сакривен|Ова корисница је раније блокирана и сакривена}}.\nДневник сакривања је наведен испод као референца:", "blocklogentry": "је блокирао [[$1]] са временом истицања од $2 $3", "reblock-logentry": "је {{GENDER:|променио|променила}} подешавања блокирања за {{GENDER:$1|корисника|корисницу}} [[$1]] са временом истека од $2 ($3)", - "blocklogtext": "Ово је дневник радњи блокирања и деблокирања корисника.\nАутоматски блокиране IP адресе нису наведене.\nПогледајте [[Special:BlockList|списак блокирања]] за списак тренутних операција забрана и блокирања.", + "blocklogtext": "Ово је евиденција радњи блокирања и деблокирања корисника.\nАутоматски блокиране IP адресе нису наведене.\nПогледајте [[Special:BlockList|листу блокирања]] за тренутно оперативне забране и блокирања.", "unblocklogentry": "је деблокирао $1", "block-log-flags-anononly": "само анонимни корисници", "block-log-flags-nocreate": "онемогућено отварање налога", @@ -2619,7 +2620,7 @@ "movepage-page-unmoved": "Страница $1 не може да се премести на $2.", "movepage-max-pages": "Највише $1 {{PLURAL:$1|страница је премештена|странице су премештене|страница је премештено}} и више не може да буде аутоматски премештено.", "movelogpage": "Дневник премештања", - "movelogpagetext": "Испод се налази списак премештања страница.", + "movelogpagetext": "Испод се налази листа свих премештања страница.", "movesubpage": "{{PLURAL:$1|Подстраница|Подстранице}}", "movesubpagetext": "Ова страница има $1 {{PLURAL:$1|подстраницу приказану|подстранице приказане|подстраница приказаних}} испод.", "movenosubpage": "Ова страница нема подстрана.", @@ -2650,7 +2651,7 @@ "exportall": "Извези све странице", "exportcuronly": "Укључи само тренутну измену, не целу историју", "exportnohistory": "----\n'''Напомена:''' извоз пуне историје страница преко овог обрасца је онемогућено из техничких разлога.", - "exportlistauthors": "Укључи целокупан списак доприносилаца за сваку страницу", + "exportlistauthors": "Укључи потпуну листу доприносилаца за сваку страницу", "export-submit": "Извези", "export-addcattext": "Додај странице из категорије:", "export-addcat": "Додај", @@ -2664,7 +2665,7 @@ "allmessagesname": "Назив", "allmessagesdefault": "Подразумевани текст", "allmessagescurrent": "Актуелни текст поруке", - "allmessagestext": "Ово је списак системских порука доступних у именском простору „Медијавики“.\nПосетите [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation Медијавики локализацију] и [https://translatewiki.net translatewiki.net] ако желите да допринесете општој локализацији Медијавикија.", + "allmessagestext": "Ово је листа системских порука доступних у именском простору „Медијавики”.\nПосетите [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation локализацију Медијавикија] и [https://translatewiki.net translatewiki.net] ако желите да допринесете општој локализацији Медијавикија.", "allmessagesnotsupportedDB": "Ова страница не може да се користи јер је '''$wgUseDatabaseMessages''' онемогућен.", "allmessages-filter-legend": "Филтер", "allmessages-filter": "Филтрирај по стању:", @@ -2750,9 +2751,9 @@ "tooltip-pt-mytalk": "{{GENDER:|Ваша}} страница за разговор", "tooltip-pt-anontalk": "Дискусија о изменама са ове IP адресе", "tooltip-pt-preferences": "{{GENDER:|Ваша}} подешавања", - "tooltip-pt-watchlist": "Списак страница чије промене надгледате", - "tooltip-pt-mycontris": "Списак {{GENDER:|ваших}} доприноса", - "tooltip-pt-anoncontribs": "Списак измена направљених са ове IP адресе", + "tooltip-pt-watchlist": "Листа страница чије промене надгледате", + "tooltip-pt-mycontris": "Листа {{GENDER:|ваших}} доприноса", + "tooltip-pt-anoncontribs": "Листа измена направљених са ове IP адресе", "tooltip-pt-login": "Предлажемо вам да се пријавите, иако то није обавезно", "tooltip-pt-login-private": "Морате да се пријавите да бисте користили овај Вики", "tooltip-pt-logout": "Одјавите се", @@ -2777,18 +2778,18 @@ "tooltip-n-mainpage-description": "Посетите главну страну", "tooltip-n-portal": "О пројекту, шта можете да радите и где да пронађете ствари", "tooltip-n-currentevents": "Пронађите информације о актуелностима", - "tooltip-n-recentchanges": "Списак недавних промена на викију", + "tooltip-n-recentchanges": "Листа недавних промена на викију", "tooltip-n-randompage": "Учитајте случајну страницу", "tooltip-n-help": "Место где можете да се информишете", - "tooltip-t-whatlinkshere": "Списак свих вики страница које воде овде", + "tooltip-t-whatlinkshere": "Листа свих вики страница које воде овде", "tooltip-t-recentchangeslinked": "Недавне промене на страницама које воде на ову страницу", "tooltip-feed-rss": "RSS фид за ову страницу", "tooltip-feed-atom": "Atom фид за ову страницу", - "tooltip-t-contributions": "Списак доприноса {{GENDER:$1|овог корисника|ове кориснице|овог корисника}}", + "tooltip-t-contributions": "Листа доприноса {{GENDER:$1|овог корисника|ове кориснице|овог корисника}}", "tooltip-t-emailuser": "Пошаљите е-поруку {{GENDER:$1|овом кориснику|овој корисници|кориснику/ци}}", "tooltip-t-info": "Више информација о овој страници", "tooltip-t-upload": "Отпремите датотеке", - "tooltip-t-specialpages": "Списак свих посебних страница", + "tooltip-t-specialpages": "Листа свих посебних страница", "tooltip-t-print": "Верзија ове странице за штампање", "tooltip-t-permalink": "Трајна веза ка овој измени странице", "tooltip-ca-nstab-main": "Погледајте страницу са садржајем", @@ -2955,7 +2956,7 @@ "file-no-thumb-animation": "Напомена: Због техничких ограничења, сличице ове датотеке неће да се анимирају.", "file-no-thumb-animation-gif": "'''Напомена: због техничких ограничења, минијатуре GIF слика високе резолуције као што је ова неће се анимирати.'''", "newimages": "Галерија нових датотека", - "imagelisttext": "Испод је списак од '''$1''' {{PLURAL:$1|датотеке|датотеке|датотека}} поређаних $2.", + "imagelisttext": "Испод се налази списак од $1 {{PLURAL:$1|датотеке|датотеке|датотека}} сортираних $2.", "newimages-summary": "Ова посебна страница приказује последње отпремљене датотеке.", "newimages-legend": "Филтер", "newimages-label": "Назив датотеке (или њен део):", @@ -3044,28 +3045,28 @@ "metadata-langitem-default": "$1", "namespacesall": "сви", "monthsall": "све", - "confirmemail": "Потврда адресе е-поште", - "confirmemail_noemail": "Нисте поставили важећу адресу е-поште у [[Special:Preferences|корисничким подешавањима]].", - "confirmemail_text": "{{SITENAME}} захтева да проверите ваљаност имејл-адресе пре него што почнете да користите функцију имејла.\nАктивирајте дугме испод да бисте послали мејл за потврду на своју адресу.\nМејл ће укључивати везу са кодом;\nучитајте везу у свом прегледачу да бисте потврдили да је ваша имејл-адреса важећа.", - "confirmemail_pending": "Код за потврду вам је већ послат имејлом.\nАко сте недавно отворили налог, можда треба да сачекате неколико минута да пристигне пре него што поново затражите нови код.", + "confirmemail": "Потврда е-адресе", + "confirmemail_noemail": "Нисте поставили важећу е-адресу у [[Special:Preferences|корисничким подешавањима]].", + "confirmemail_text": "{{SITENAME}} захтева да проверите ваљаност е-адресе пре него што почнете да користите функцију е-поште.\nАктивирајте дугме испод да бисте послали поруку са потврдом на своју адресу.\nПорука ће укључивати везу са кодом;\nучитајте везу у свом прегледачу да бисте потврдили да је ваша е-адреса важећа.", + "confirmemail_pending": "Код за потврду вам је већ послат е-поштом.\nАко сте недавно отворили налог, можда треба да сачекате неколико минута да пристигне пре него што поново затражите нови код.", "confirmemail_send": "Пошаљи код за потврду", "confirmemail_sent": "Потврдна порука је послата.", - "confirmemail_oncreate": "Кôд за потврду је послат на вашу имејл-адресу.\nОвај кôд није неопходан за пријављивање, али ћете морати да га наведете пре омогућавања било каквих функција заснованих на имејлу на викију.", - "confirmemail_sendfailed": "{{SITENAME}} не може да пошаље е-поруку за потврду.\nПроверите да ли је адреса е-поште садржи неважеће знаке.\n\nПошиљалац је вратио грешку: $1", + "confirmemail_oncreate": "Кôд за потврду је послат на вашу е-адресу.\nОвај кôд није неопходан за пријављивање, али ћете морати да га наведете пре омогућавања било каквих функција заснованих на е-пошти на викију.", + "confirmemail_sendfailed": "{{SITENAME}} не може да пошаље поруку са потврдом.\nПроверите да ли е-адреса садржи неважеће знакове.\n\nПошиљалац је вратио грешку: $1", "confirmemail_invalid": "Неважећи код за потврду.\nКод је можда истекао.", - "confirmemail_needlogin": "$1 да бисте потврдили адресу е-поште.", - "confirmemail_success": "Ваша адреса е-поште је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.", - "confirmemail_loggedin": "Ваша адреса е-поште је сада потврђена.", - "confirmemail_subject": "{{SITENAME}} – потврда адресе е-поште", - "confirmemail_body": "Неко, вероватно Ви, са IP адресе $1,\nрегистровао је налог „$2“ са овом имејл адресом на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и активирали функцију имејла на пројекту {{SITENAME}}, отворите ова у прегледачу:\n\n$3\n\nАко ви *нисте* регистровали налог, пратите ову везу\nда бисте отказали потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче у $4.", - "confirmemail_body_changed": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали функцију имејла, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $6 у $7", - "confirmemail_body_set": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nфункцију имејла на {{SITENAME}}, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $4.", - "confirmemail_invalidated": "Потврда адресе е-поште је отказана", + "confirmemail_needlogin": "$1 да бисте потврдили е-адресу.", + "confirmemail_success": "Ваша е-адреса је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.", + "confirmemail_loggedin": "Ваша е-адреса је сада потврђена.", + "confirmemail_subject": "{{SITENAME}} – потврда е-адресе", + "confirmemail_body": "Неко, вероватно Ви, са IP адресе $1,\nрегистровао је налог „$2“ са овом е-адресом на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и активирали функције е-поште на пројекту {{SITENAME}}, отворите ову везу у прегледачу:\n\n$3\n\nАко ви *нисте* регистровали налог, пратите ову везу\nда бисте отказали потврду е-адресе:\n\n$5\n\nОвај код за потврду истиче у $4.", + "confirmemail_body_changed": "Неко, вероватно Ви, са IP адресе $1,\nпроменио је е-адресу налога „$2” у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали функције е-поште, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду е-адресе:\n\n$5\n\nОвај код за потврду истиче $6 у $7", + "confirmemail_body_set": "Неко, вероватно Ви, са IP адресе $1,\nпроменио је е-адресу налога „$2” у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nфункције е-поште на пројекту {{SITENAME}}, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду е-адресе:\n\n$5\n\nОвај код за потврду истиче $4.", + "confirmemail_invalidated": "Потврда е-адресе је отказана", "invalidateemail": "Отказивање потврде е-поште", - "notificationemail_subject_changed": "Регистрована имејл адреса на пројекту {{SITENAME}} је промењена", - "notificationemail_subject_removed": "Регистрована имејл адреса на пројекту {{SITENAME}} је уклоњена", - "notificationemail_body_changed": "Неко, вероватно Ви је променио имејл адресу налога из $2“ у „$3“ са IP адресе $1 на сајту {{SITENAME}}.\n\nАко ово нисте били Ви, одмах обавестите администраторе сајта.", - "notificationemail_body_removed": "Неко, вероватно Ви, с IP адресе $1, \nуклонио је имејл адресу за налог „$2“ на {{SITENAME}}.\n\nАко ово нисте били Ви, контактирајте администраторе сајта одмах.", + "notificationemail_subject_changed": "Регистрована е-адреса на пројекту {{SITENAME}} је промењена", + "notificationemail_subject_removed": "Регистрована е-адреса на пројекту {{SITENAME}} је уклоњена", + "notificationemail_body_changed": "Неко, вероватно Ви, са IP адресе $1,\nпроменио је е-адресу налога из „$2” у „$3” на пројекту {{SITENAME}}.\n\nАко ово нисте били Ви, одмах се обратите администратору сајта.", + "notificationemail_body_removed": "Неко, вероватно Ви, са IP адресе $1, \nуклонио је е-адресу налога „$2” на пројекту {{SITENAME}}.\n\nАко ово нисте били Ви, одмах се обратите администратору сајта.", "scarytranscludedisabled": "[Међувики укључивање шаблона је онемогућено]", "scarytranscludefailed": "[Добављање шаблона за $1 није успело]", "scarytranscludefailed-httpstatus": "[Не могу да преузмем шаблон $1: HTTP $2]", @@ -3348,8 +3349,8 @@ "tag-mw-undo": "поништење", "tag-mw-undo-description": "Измене које поништавају претходне измене", "tags-title": "Ознаке", - "tags-intro": "На овој страници је наведен списак ознака с којима програм може да означи измене и његово значење.", - "tags-tag": "Назив ознаке", + "tags-intro": "Ова страница наводи ознаке којима софтвер може да означи измене, те њихово значење.", + "tags-tag": "Име ознаке", "tags-display-header": "Изглед на списковима промена", "tags-description-header": "Опис значења", "tags-source-header": "Извор", @@ -3521,7 +3522,7 @@ "logentry-newusers-newusers": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог", "logentry-newusers-create": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог", "logentry-newusers-create2": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3", - "logentry-newusers-byemail": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3 и лозинка је послата на имејл", + "logentry-newusers-byemail": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3 и лозинка је послата на е-пошту", "logentry-newusers-autocreate": "$1 је аутоматски {{GENDER:$2|отворио|отворила}} кориснички налог", "logentry-protect-move_prot": "$1 је {{GENDER:$2|преместио|преместила}} подешавања заштите са $4 на $3", "logentry-protect-unprotect": "$1 je {{GENDER:$2|скинуо|скинула}} заштиту са странице $3", @@ -3536,7 +3537,7 @@ "logentry-upload-overwrite": "$1 је {{GENDER:$2|отпремио|отпремила}} нову верзију датотеке $3", "logentry-upload-revert": "$1 је {{GENDER:$2|вратио|вратила}} датотеку $3 на старију верзију", "log-name-managetags": "Дневник управљања ознакама", - "log-description-managetags": "На овој страници се налази списак измена у вези [[Special:Tags|ознака]]. Дневник садржи само радње које су ручно извршили администратори; уноси за ознаке које је направио или избрисао вики софтвер, а не налазе се у овом дневнику.", + "log-description-managetags": "Ова страница наводи задатке управљања у вези са [[Special:Tags|ознакама]]. Евиденција садржи само радње које су ручно извршили администратори; уноси за ознаке које је направио или избрисао вики софтвер, а не налазе се у овој евиденцији.", "logentry-managetags-create": "$1 је {{GENDER:$2|направио|направила}} ознаку „$4“", "logentry-managetags-delete": "$1 је {{GENDER:$2|избрисао|избрисала}} ознаку „$4” (уклоњена је из $5 {{PLURAL:$5|измене или уноса у дневнику|измена и/или уноса у дневнику}})", "logentry-managetags-activate": "$1 је {{GENDER:$2|активирао|активирала}} ознаку „$4“ за употребу од стране корисника и ботова", @@ -3786,7 +3787,7 @@ "authmanager-domain-help": "Домен за спољашњу потврду идентитета.", "authmanager-retype-help": "Поновите лозинку да би сте потврдили.", "authmanager-email-label": "Е-пошта", - "authmanager-email-help": "Адреса е-поште:", + "authmanager-email-help": "Е-адреса", "authmanager-realname-label": "Право име", "authmanager-realname-help": "Право име корисника", "authmanager-provider-password": "Потврда идентитета лозинком", @@ -3853,7 +3854,7 @@ "pagedata-bad-title": "Невалидан наслов: $1.", "unregistered-user-config": "Из безбедоносних разлога, јаваскрипт, Це-Ес-Ес и ЈСОН корисничке подстранице не могу бити учитане за нерегистроване кориснике.", "passwordpolicies": "Правила за лозинке", - "passwordpolicies-summary": "Ово је списак делотворних смерница за лозинке за корисничке групе одређене на овом викију.", + "passwordpolicies-summary": "Ово је листа делотворних смерница за лозинке за корисничке групе дефинисане на овом викију.", "passwordpolicies-group": "Група", "passwordpolicies-policies": "Правила", "passwordpolicies-policy-display": "$1 ($2)", diff --git a/languages/i18n/sv.json b/languages/i18n/sv.json index e87f2ea58e..01d6bd6f56 100644 --- a/languages/i18n/sv.json +++ b/languages/i18n/sv.json @@ -128,6 +128,7 @@ "tog-useeditwarning": "Varna mig om jag lämnar en redigeringssida med osparade ändringar", "tog-prefershttps": "Använd alltid en säker anslutning medan jag är inloggad", "tog-showrollbackconfirmation": "Visa en bekräftelsedialog när man klickar på en tillbakarullningslänk", + "tog-showrollbackconfirmation-prerelease-warning": "OBS: Denna funktion är ännu inte tillgänglig. Om du ändrar denna inställning nu kommer ditt val kommas ihåg [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status när funktionen släpps].", "underline-always": "Alltid", "underline-never": "Aldrig", "underline-default": "Webbläsarens eller utseendets standardinställning", @@ -2324,6 +2325,9 @@ "deleting-backlinks-warning": "Varning:\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Andra sidor]] länkar till eller inkluderar sidan som du är på väg att radera.", "deleting-subpages-warning": "Varning: Sidan du håller på att radera har [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|en undersida|$1 undersidor|51=över 50 undersidor}}]].", "rollback": "Rulla tillbaka ändringar", + "rollback-confirmation-confirm": "Var god bekräfta:", + "rollback-confirmation-yes": "Rulla tillbaka", + "rollback-confirmation-no": "Avbryt", "rollbacklink": "rulla tillbaka", "rollbacklinkcount": "rulla tillbaka $1 {{PLURAL:$1|redigering|redigeringar}}", "rollbacklinkcount-morethan": "rulla tillbaka mer än $1 {{PLURAL:$1|redigering|redigeringar}}", @@ -3110,6 +3114,7 @@ "confirm-unwatch-top": "Ta bort denna sida från din bevakningslista?", "confirm-rollback-button": "OK", "confirm-rollback-top": "Återställ redigeringar på denna sida?", + "confirm-rollback-bottom": "Denna åtgärd kommer rulla tillbaka de markerade ändringarna på denna sida direkt.", "confirm-mcrrestore-title": "Återställ en sidversion", "confirm-mcrundo-title": "Ångra en ändring", "mcrundofailed": "Misslyckades att ångra", diff --git a/languages/i18n/th.json b/languages/i18n/th.json index 252f7f52b8..b381f12d7f 100644 --- a/languages/i18n/th.json +++ b/languages/i18n/th.json @@ -372,7 +372,7 @@ "perfcachedts": "ข้อมูลต่อไปนี้ถูกเก็บในแคชและถูกปรับล่าสุดเมื่อ $1 มีผลลัพธ์สูงสุด $4 รายการในแคชได้", "querypage-no-updates": "ขณะนี้ปิดใช้งานการปรับหน้านี้ \nข้อมูลในที่นี้จะไม่รีเฟรชเป็นปัจจุบัน", "viewsource": "ดูต้นฉบับ", - "viewsource-title": "ดูโค้ดสำหรับ $1", + "viewsource-title": "ดูต้นฉบับสำหรับ $1", "actionthrottled": "ปฏิบัติการถูกจำกัด", "actionthrottledtext": "เพื่อเป็นมาตรการป้องกันการละเมิด คุณจึงถูกจำกัดมิให้กระทำสิ่งนี้ติดต่อกันหลายครั้งเกินไปในช่วงระยะเวลาสั้น ๆ ซึ่งขณะนี้คุณเลยขีดจำกัดนี้แล้ว \nกรุณารอสักครู่แล้วลองอีกครั้ง", "protectedpagetext": "หน้านี้ถูกล็อกเพื่อป้องกันการแก้ไขหรือปฏิบัติการอื่น", @@ -2201,7 +2201,7 @@ "editcomment": "คำอธิบายการแก้ไขคือ: $1", "revertpage": "ย้อนการแก้ไขโดย [[Special:Contributions/$2|$2]] ([[User talk:$2|คุย]]) ไปยังรุ่นแก้ไขล่าสุดโดย [[User:$1|$1]]", "revertpage-nouser": "ย้อนการแก้ไขโดยผู้ใช้ไม่ระบุชื่อไปยังรุ่นล่าสุดโดย {{GENDER:$1|[[User:$1|$1]]}}", - "rollback-success": "ย้อนการแก้ไขโดย $1; \nเปลี่ยนกลับไปรุ่นล่าสุดโดย $2", + "rollback-success": "ย้อนการแก้ไขโดย {{GENDER:$3|$1}};\nเปลี่ยนกลับไปรุ่นล่าสุดโดย {{GENDER:$4|$2}}", "sessionfailure-title": "เซสชันล้มเหลว", "sessionfailure": "ดูเหมือนมีปัญหากับเซสชันการเข้าสู่ระบบของคุณ\nการกระทำนี้ถูกยกเลิกเพื่อเป็นการป้องกันการลักลอบเซสชันไว้ก่อน \nกรุณากรอกแบบฟอร์มใหม่อีกครั้ง", "changecontentmodel-title-label": "ชื่อหน้า:", @@ -2219,7 +2219,7 @@ "movedarticleprotection": "ย้ายการตั้งค่าการล็อกจาก \"[[$2]]\" ไป \"[[$1]]\"", "protectedarticle-comment": "ป้องกัน \"[[$1]]\"", "modifiedarticleprotection-comment": "{{GENDER:$2|}}เปลี่ยนระดับการล็อกสำหรับ \"[[$1]]\"", - "unprotectedarticle-comment": "{{GENDER:$2|}}ปลดล็อก \"[[$1]]\"", + "unprotectedarticle-comment": "{{GENDER:$2|ยกเลิกการป้องกัน}}จาก \"[[$1]]\"", "protect-title": "เปลี่ยนระดับการล็อกสำหรับ \"$1\"", "protect-title-notallowed": "ดูระดับการล็อกของ \"$1\"", "prot_1movedto2": "ย้าย [[$1]] เป็น [[$2]]", @@ -3395,10 +3395,10 @@ "log-action-filter-newusers-byemail": "การสร้างโดยรหัสผ่านที่ส่งทางอีเมล", "log-action-filter-patrol-patrol": "การตรวจสอบหน้าด้วยมือ", "log-action-filter-patrol-autopatrol": "การตรวจสอบหน้าอัตโนมัติ", - "log-action-filter-protect-protect": "การล็อก", - "log-action-filter-protect-modify": "การแก้ไขการล็อก", - "log-action-filter-protect-unprotect": "ปลดล็อก", - "log-action-filter-protect-move_prot": "การล็อกที่ถูกย้าย", + "log-action-filter-protect-protect": "การป้องกัน", + "log-action-filter-protect-modify": "การแก้ไขการป้องกัน", + "log-action-filter-protect-unprotect": "เลิกป้องกัน", + "log-action-filter-protect-move_prot": "การป้องกันที่ถูกย้าย", "log-action-filter-rights-rights": "การเปลี่ยนด้วยมือ", "log-action-filter-rights-autopromote": "การเปลี่ยนอัตโนมัติ", "log-action-filter-suppress-event": "การระงับปูม", diff --git a/languages/i18n/tr.json b/languages/i18n/tr.json index 2ad49150b6..73731503cf 100644 --- a/languages/i18n/tr.json +++ b/languages/i18n/tr.json @@ -2193,10 +2193,10 @@ "watchlistanontext": "Lütfen izleme listenizdeki maddeleri görmek ya da değiştirmek için oturum açın.", "watchnologin": "Oturum açık değil.", "addwatch": "İzleme listesine ekle", - "addedwatchtext": "\"[[:$1]]\" sayfası [[Special:Watchlist|izleme listenize]] eklenmiştir.\nBundan sonra, bu sayfaya ve ilgili tartışma sayfasına yapılacak değişiklikler burada listelenecek.", + "addedwatchtext": "\"[[:$1]]\" ve ona ait tartışma sayfası [[Special:Watchlist|izleme listenize]] eklenmiştir.", "addedwatchtext-short": "\"$1\" sayfası izleme listenize eklendi.", "removewatch": "İzleme listesinden kaldır", - "removedwatchtext": "\"[[:$1]]\" sayfası, tartışma sayfası ile birlikte [[Special:Watchlist|izleme listenizden]] silinmiştir.", + "removedwatchtext": "\"[[:$1]]\" ve ona ait tartışma sayfası ile birlikte [[Special:Watchlist|izleme listenizden]] silinmiştir.", "removedwatchtext-short": "\"$1\" sayfası izleme listenizden çıkarıldı.", "watch": "izle", "watchthispage": "Sayfayı izle", @@ -3419,7 +3419,7 @@ "expand_templates_generate_xml": "XML derleyici ağacını göster", "expand_templates_generate_rawhtml": "Ham HTML göster", "expand_templates_preview": "Önizleme", - "expand_templates_preview_fail_html": "{{SITENAME}} işlenmemiş HTML koduna izin verdiği ve oturum verilerinde kayıp yaşandığı için, ön izleme, JavaScript saldırılarına karşı önlem olarak gizlendi.\n\nEğer meşru bir ön izleme girişimi idiyse, tekrar deneyiniz.\nYine de çalışmıyorsa, [[Special:UserLogout|oturum kapamayı]] ve tekrar açmayı deneyin.", + "expand_templates_preview_fail_html": "{{SITENAME}} işlenmemiş HTML koduna izin verdiği ve oturum verilerinde kayıp yaşandığı için, ön izleme, JavaScript saldırılarına karşı önlem olarak gizlendi.\n\nEğer bu meşru bir ön izleme girişimi idiyse, tekrar deneyiniz.\nYine de çalışmıyorsa, [[Special:UserLogout|oturum kapatıp]] tekrar açmayı deneyin ve tarayıcınızın bu siteden çerezlere izin verip vermediğini kontrol edin.", "expand_templates_preview_fail_html_anon": "{{SITENAME}} işlenmemiş HTML koduna izin verdiği ve oturum verilerinde kayıp yaşandığı için, ön izleme, JavaScript saldırılarına karşı önlem olarak gizlendi.\n\nEğer meşru bir ön izleme girişimi idiyse, lütfen [[Special:UserLogin|oturum açarak]] tekrar deneyin.", "expand_templates_input_missing": "En azından bazı giriş viki metni sağlamak zorundasınız.", "pagelanguage": "Sayfanın dilini değiştir", diff --git a/languages/i18n/uk.json b/languages/i18n/uk.json index fad362d280..34b32bf298 100644 --- a/languages/i18n/uk.json +++ b/languages/i18n/uk.json @@ -125,6 +125,7 @@ "tog-useeditwarning": "Попереджати мене, якщо я залишаю сторінку редагування з незбереженими змінами", "tog-prefershttps": "Завжди використовувати безпечне з'єднання при вході в систему", "tog-showrollbackconfirmation": "Показати підтверджувальне вікно при натисканні на посилання відкоту", + "tog-showrollbackconfirmation-prerelease-warning": "Зверніть увагу: ця функція ще не доступна. Якщо Ви оберете зараз цю опцію, Ваш вибір буде збережено, [https://meta.wikimedia.org/wiki/WMDE_Technical_Wishes/Rollback#Status коли функцію буде реалізовано].", "underline-always": "Завжди", "underline-never": "Ніколи", "underline-default": "Використовувати налаштування браузера", @@ -2336,6 +2337,9 @@ "deleting-backlinks-warning": "Попередження: [[Special:WhatLinksHere/{{FULLPAGENAME}}|інші сторінки]] посилаються або містять сторінку, яку Ви маєте намір видалити.", "deleting-subpages-warning": "Попередження: Сторінка, яку Ви маєте намір вилучити, має [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1 підсторінку|$1 підсторінки|$1 підсторінок|51=понад 50 підсторінок}}]].", "rollback": "Відкинуто редагування", + "rollback-confirmation-confirm": "Будь ласка, підтвердіть:", + "rollback-confirmation-yes": "Відкинути", + "rollback-confirmation-no": "Скасувати", "rollbacklink": "відкинути", "rollbacklinkcount": "відкинути $1 {{PLURAL:$1|редагування|редагування|редагувань}}", "rollbacklinkcount-morethan": "відкинути понад $1 {{PLURAL:$1|редагування|редагування|редагувань}}", @@ -3162,6 +3166,7 @@ "confirm-unwatch-top": "Вилучити цю сторінку з вашого списку спостереження?", "confirm-rollback-button": "Гаразд", "confirm-rollback-top": "Відкотити редагування цієї сторінки?", + "confirm-rollback-bottom": "Ця дія миттєво відкотить обрані зміни на цій сторінці.", "confirm-mcrrestore-title": "Відновити версію", "confirm-mcrundo-title": "Скасувати зміну", "mcrundofailed": "Помилка скасування", diff --git a/languages/i18n/zh-hant.json b/languages/i18n/zh-hant.json index 5e2cb469a9..c1b21b7f91 100644 --- a/languages/i18n/zh-hant.json +++ b/languages/i18n/zh-hant.json @@ -2943,7 +2943,7 @@ "spam_blanking": "所有修訂均包含 $1 連結,清空中", "spam_deleting": "所有修訂均包含 $1 連結,刪除中", "simpleantispam-label": "防垃圾訊息檢查用。\n請 勿 填寫此欄位!", - "pageinfo-title": "\"$1\" 的資訊", + "pageinfo-title": "「$1」的資訊", "pageinfo-not-current": "抱歉,無法提供先前修訂的資訊。", "pageinfo-header-basic": "基本資訊", "pageinfo-header-edits": "編輯歷史", @@ -3513,7 +3513,7 @@ "logentry-newusers-autocreate": "已自動{{GENDER:$2|建立}}使用者帳號 $1", "logentry-protect-move_prot": "$1 {{GENDER:$2|已移動}}保護設定從 $4 至 $3", "logentry-protect-unprotect": "$1 {{GENDER:$2|已移除}} $3 的保護", - "logentry-protect-protect": "$1 {{GENDER:$2|已保護}} $3 $4", + "logentry-protect-protect": "$1 {{GENDER:$2|pó-hō͘ liáu}} $3 $4", "logentry-protect-protect-cascade": "$1 {{GENDER:$2|已保護}} $3 $4 [連鎖]", "logentry-protect-modify": "$1 {{GENDER:$2|已變更}} $3 的保護層級 $4", "logentry-protect-modify-cascade": "$1 {{GENDER:$2|已變更}} $3 的保護層級 $4 [連鎖]", diff --git a/languages/messages/MessagesNqo.php b/languages/messages/MessagesNqo.php index 855e0144db..4de60f3f16 100644 --- a/languages/messages/MessagesNqo.php +++ b/languages/messages/MessagesNqo.php @@ -10,6 +10,25 @@ $rtl = true; +$namespaceNames = [ + NS_MEDIA => 'ߟߊߛߋߢߊߥߙߍ', + NS_SPECIAL => 'ߞߙߍߞߙߍߣߍ߲', + NS_TALK => 'ߢߊߝߐߞߣߍ', + NS_USER => 'ߟߊߓߊ߯ߙߟߊ', + NS_USER_TALK => 'ߟߊߓߊ߯ߙߟߊ ߟߊ߫ ߢߊߝߐߞߣߍ', + NS_PROJECT_TALK => '$1 ߢߊߝߐߞߣߍ', + NS_FILE => 'ߞߐߕߐ߮', + NS_FILE_TALK => 'ߞߐߕߐ߮ ߢߊߝߐߞߣߍ', + NS_MEDIAWIKI => 'ߡߘߌߦߊߥߞߌ', + NS_MEDIAWIKI_TALK => 'ߡߘߌߦߊߥߞߌ ߢߊߝߐߞߣߍ', + NS_TEMPLATE => 'ߞߙߊߞߏ', + NS_TEMPLATE_TALK => 'ߞߙߊߞߏ ߢߊߝߐߞߣߍ', + NS_HELP => 'ߡߊ߬ߘߍ߬ߡߍ߲߬ߠߌ߲', + NS_HELP_TALK => 'ߡߊ߬ߘߍ߬ߡߍ߲߬ߠߌ߲ ߢߊߝߐߞߣߍ', + NS_CATEGORY => 'ߦߌߟߡߊ', + NS_CATEGORY_TALK => 'ߦߌߟߡߊ ߢߊߝߐߞߣߍ', +]; + $digitTransformTable = [ '0' => '߀', # U+07C0 '1' => '߁', # U+07C1 diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php index 3403e82a06..3476a32a6a 100644 --- a/maintenance/Maintenance.php +++ b/maintenance/Maintenance.php @@ -862,14 +862,7 @@ abstract class Maintenance { $this->setParam( $options, $option, $param ); } else { $bits = explode( '=', $option, 2 ); - if ( count( $bits ) > 1 ) { - $option = $bits[0]; - $param = $bits[1]; - } else { - $param = 1; - } - - $this->setParam( $options, $option, $param ); + $this->setParam( $options, $bits[0], $bits[1] ?? 1 ); } } elseif ( $arg == '-' ) { # Lonely "-", often used to indicate stdin or stdout. @@ -1252,11 +1245,8 @@ abstract class Maintenance { } if ( isset( $this->mOptions['wiki'] ) ) { $bits = explode( '-', $this->mOptions['wiki'], 2 ); - if ( count( $bits ) == 1 ) { - $bits[] = ''; - } define( 'MW_DB', $bits[0] ); - define( 'MW_PREFIX', $bits[1] ); + define( 'MW_PREFIX', $bits[1] ?? '' ); } elseif ( isset( $this->mOptions['server'] ) ) { // Provide the option for site admins to detect and configure // multiple wikis based on server names. This offers --server diff --git a/maintenance/cleanupInvalidDbKeys.php b/maintenance/cleanupInvalidDbKeys.php index abae4f4379..eb45cfcce8 100644 --- a/maintenance/cleanupInvalidDbKeys.php +++ b/maintenance/cleanupInvalidDbKeys.php @@ -121,8 +121,7 @@ TEXT * @param array $tableParams A child array of self::$tables */ protected function cleanupTable( $tableParams ) { - $table = $tableParams[0]; - $prefix = $tableParams[1]; + list( $table, $prefix ) = $tableParams; $idField = $tableParams['idField'] ?? "{$prefix}_id"; $nsField = $tableParams['nsField'] ?? "{$prefix}_namespace"; $titleField = $tableParams['titleField'] ?? "{$prefix}_title"; diff --git a/maintenance/convertLinks.php b/maintenance/convertLinks.php index af60eaa2ba..59820a5a8f 100644 --- a/maintenance/convertLinks.php +++ b/maintenance/convertLinks.php @@ -126,7 +126,6 @@ class ConvertLinks extends Maintenance { $res = $dbw->query( "SELECT COUNT(*) AS count FROM $links" ); $row = $dbw->fetchObject( $res ); $numRows = $row->count; - $dbw->freeResult( $res ); if ( $numRows == 0 ) { $this->output( "Updating schema (no rows to convert)...\n" ); @@ -168,7 +167,6 @@ class ConvertLinks extends Maintenance { } } } - $dbw->freeResult( $res ); $dbw->bufferResults( true ); $this->output( "Finished loading IDs.\n\n" ); $this->performanceLog( @@ -214,7 +212,6 @@ class ConvertLinks extends Maintenance { $numBadLinks++; } } - $dbw->freeResult( $res ); # $this->output( "rowOffset: $rowOffset\ttuplesAdded: " # . "$tuplesAdded\tnumBadLinks: $numBadLinks\n" ); if ( $tuplesAdded != 0 ) { diff --git a/maintenance/dictionary/mediawiki.dic b/maintenance/dictionary/mediawiki.dic index fc17a3dd0e..45457f543f 100644 --- a/maintenance/dictionary/mediawiki.dic +++ b/maintenance/dictionary/mediawiki.dic @@ -2425,7 +2425,6 @@ mkdir mms mobile mobileformat -mobilelanding mobileview modified modifiedarticleprotection diff --git a/maintenance/dumpTextPass.php b/maintenance/dumpTextPass.php index f515df70f4..61c63e996b 100644 --- a/maintenance/dumpTextPass.php +++ b/maintenance/dumpTextPass.php @@ -839,6 +839,7 @@ TEXT if ( $newAddress === false ) { return false; } + $newAddress = trim( $newAddress ); if ( strpos( $newAddress, ':' ) === false ) { $newAddress = SqlBlobStore::makeAddressFromTextId( intval( $newAddress ) ); } diff --git a/maintenance/includes/BackupDumper.php b/maintenance/includes/BackupDumper.php index a9e757e7b4..0b450a64b0 100644 --- a/maintenance/includes/BackupDumper.php +++ b/maintenance/includes/BackupDumper.php @@ -26,6 +26,7 @@ */ require_once __DIR__ . '/../Maintenance.php'; +require_once __DIR__ . '/../../includes/export/WikiExporter.php'; use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\LoadBalancer; @@ -87,6 +88,7 @@ abstract class BackupDumper extends Maintenance { $this->registerOutput( 'gzip', DumpGZipOutput::class ); $this->registerOutput( 'bzip2', DumpBZip2Output::class ); $this->registerOutput( 'dbzip2', DumpDBZip2Output::class ); + $this->registerOutput( 'lbzip2', DumpLBZip2Output::class ); $this->registerOutput( '7zip', Dump7ZipOutput::class ); $this->registerFilter( 'latest', DumpLatestFilter::class ); @@ -97,7 +99,7 @@ abstract class BackupDumper extends Maintenance { $this->addOption( 'plugin', 'Load a dump plugin class. Specify as [:].', false, true, false, true ); $this->addOption( 'output', 'Begin a filtered output stream; Specify as :. ' . - 's: file, gzip, bzip2, 7zip, dbzip2', false, true, false, true ); + 's: file, gzip, bzip2, 7zip, dbzip2, lbzip2', false, true, false, true ); $this->addOption( 'filter', 'Add a filter on an output branch. Specify as ' . '[:]. s: latest, notalk, namespace', false, true, false, true ); $this->addOption( 'report', 'Report position and speed after every n pages processed. ' . @@ -162,8 +164,7 @@ abstract class BackupDumper extends Maintenance { $options = $this->orderedOptions; foreach ( $options as $arg ) { - $opt = $arg[0]; - $param = $arg[1]; + list( $opt, $param ) = $arg; switch ( $opt ) { case 'plugin': diff --git a/maintenance/manageForeignResources.php b/maintenance/manageForeignResources.php new file mode 100644 index 0000000000..54554b8c5b --- /dev/null +++ b/maintenance/manageForeignResources.php @@ -0,0 +1,86 @@ +addDescription( <<addArg( 'action', 'One of "update", "verify" or "make-sri"', true ); + $this->addArg( 'module', 'Name of a single module (Default: all)', false ); + $this->addOption( 'verbose', 'Be verbose', false, false, 'v' ); + } + + /** + * @return bool + * @throws Exception + */ + public function execute() { + global $IP; + $frm = new ForeignResourceManager( + "{$IP}/resources/lib/foreign-resources.yaml", + "{$IP}/resources/lib", + function ( $text ) { + $this->output( $text ); + }, + function ( $text ) { + $this->error( $text ); + }, + function ( $text ) { + if ( $this->hasOption( 'verbose' ) ) { + $this->output( $text ); + } + } + ); + + $action = $this->getArg( 0 ); + $module = $this->getArg( 1, 'all' ); + return $frm->run( $action, $module ); + } +} + +$maintClass = ManageForeignResources::class; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/mysql.php b/maintenance/mysql.php index 34a6cb6951..c1e403cd3a 100644 --- a/maintenance/mysql.php +++ b/maintenance/mysql.php @@ -137,9 +137,7 @@ class MysqlMaintenance extends Maintenance { } elseif ( substr_count( $realServer, ':' ) == 1 ) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location - $hostAndSocket = explode( ':', $realServer, 2 ); - $realServer = $hostAndSocket[0]; - $socket = $hostAndSocket[1]; + list( $realServer, $socket ) = explode( ':', $realServer, 2 ); } if ( $dbName === false ) { diff --git a/maintenance/resources/foreign-resources.yaml b/maintenance/resources/foreign-resources.yaml deleted file mode 100644 index 3ecd12e8a3..0000000000 --- a/maintenance/resources/foreign-resources.yaml +++ /dev/null @@ -1,210 +0,0 @@ -### Format of this file -# -# The top-level keys are directory names (under resources/lib/). -# They should match module names (as registered in Resources.php), but there are exceptions. -# Each top-level key holds a resource descriptor that must have one of -# the following `type` values: -# -# - `tar`: For tarball archive (may be gzip-compressed). -# - `file: For a plain file. -# - `multi-file`: For multiple plain files. -# -### Type tar -# -# The `src` and `integrity` keys are required. -# -# * `src`: Full URL to the remote resource. -# * `integrity`: Cryptographic hash (integrity metadata format per ). -# * `dest`: An object mapping paths to files or directory from the remote resource to a destination -# in the module directory. The value of key in dest may be omitted, which will extract the key -# directly to the module directory. -# -### Type file -# -# The `src` and `integrity` keys are required. -# -# * `src`: Full URL to the remote resource. -# * `integrity`: Cryptographic hash (integrity metadata format per ). -# * `dest`: The name of the file in the module directory. Default: Basename of URL. -# -### Type multi-file -# -# The `files` key is required. -# -# * `files`: An object mapping destination paths to an object containing `src` and `integrity` -# keys. - -CLDRPluralRuleParser: - type: file - src: https://raw.githubusercontent.com/santhoshtr/CLDRPluralRuleParser/0dda851/src/CLDRPluralRuleParser.js - integrity: sha384-M4taeYYG2+9Ob1/La16iO+zlRRmBV5lBR3xUKkQT6kfkJ0aLbCi6yc0RYI1BDzdh - -easy-deflate: - type: multi-file - files: - deflate.js: - src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/deflate.js - integrity: sha384-sHnZLDSWMUhA2w9ygkzCK8YFvoh/fQKY6lXMbvmrYzjuNURiLB0DZFCDNMpGyZ77 - easydeflate.js: - src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/easydeflate.js - integrity: sha384-EwPfP2RMkDPa1HkzQsXgzTsy1KEjcIzQPA1HDS/JPHjvEMvVUsCxWwm1oXql/jk2 - inflate.js: - src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/inflate.js - integrity: sha384-hMg44Hw424mUYvmzKl0JT4J8UU/1YYhTiGRtR0YX/MXNLK9qWTK0d62FBCDGxmxw - README.md: - src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/README.md - integrity: sha384-6kwcfCLivvqXBZy2ATyya+mTVWLk3eaQyBdC6tbpBtkygnBrM2SNkq3jz/l7IkvP - -html5shiv: - type: file - src: https://raw.githubusercontent.com/aFarkas/html5shiv/3.7.3/src/html5shiv.js - integrity: sha384-RPXhaTf22QktT8KTwZ6bUz/C+7CnccaIw5W/y/t0FW5WSDGj3wc3YtRIJC0w47in - -jquery: - type: file - src: https://code.jquery.com/jquery-3.3.1.js - # Integrity from link modals https://code.jquery.com/jquery/ - integrity: sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60= - dest: jquery.js - -jquery.client: - type: tar - src: https://registry.npmjs.org/jquery-client/-/jquery-client-2.0.2.tgz - integrity: sha256-8c8nBbBykHEMc4I7ksdKJvvw/P7WkaC2X46RTPdz/pw= - dest: - package/AUTHORS.txt: - package/jquery.client.js: - package/LICENSE-MIT: - package/README.md: - -jquery.cookie: - type: multi-file - files: - jquery.cookie.js: - src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/jquery.cookie.js - integrity: sha384-Xxq63E9KDgzUJ6WPNPqVeOtRIwZyx6y9DzEwY2u6LYKSnWrjSoGtWSKmTindYBf2 - MIT-LICENSE.txt: - src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/MIT-LICENSE.txt - integrity: sha384-zYsGf3KJ7S0AhOICjcoh0kkn7aGZlzYUXXX5xz8dwR9KjLMM+/JPR2g/jVOGGeId - CHANGELOG.md: - src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/CHANGELOG.md - integrity: sha384-SQOHhLc7PHxHDQpGE/zv9XfXKL0A7OBu8kuyVDnHVp+zSoWyRw4xUJ+LSm5ql4kS - -jquery.form: - type: file - src: https://raw.githubusercontent.com/jquery-form/form/ff80d9ddf4/jquery.form.js - integrity: sha384-h4G2CrcSbixzMvrrK259cNBYaL/vS1D4+KdUN9NJDzQnTU1bQ6Avluget+Id13M7 - dest: jquery.form.js - -jquery.fullscreen: - type: file - src: https://raw.githubusercontent.com/theopolisme/jquery-fullscreen/v2.1.0/jquery.fullscreen.js - integrity: sha384-G4KPs2d99tgcsyUnJ3eeZ1r2hEKDwZfc4+/xowL/LIemq2VVwEE8HpVAWt4WYNLR - dest: jquery.fullscreen.js - -jquery.hoverIntent: - type: file - src: https://raw.githubusercontent.com/briancherne/jquery-hoverIntent/823603fdac/jquery.hoverIntent.js - integrity: sha384-lca0haN0hqFGGh2aYUhtAgX9dhVHfQnTADH4svDeM6gcXnL7aFGeAi1NYwipDMyS - dest: jquery.hoverIntent.js - -jquery.jStorage: - type: file - src: https://raw.githubusercontent.com/andris9/jStorage/v0.4.12/jstorage.js - integrity: sha384-geMeN8k803kPp6cqRL4VNfuSM1L8DcbKRk0St/KHJzxgpX9S0y9FA6HxA/JgucrJ - dest: jstorage.js - -jquery.throttle-debounce: - type: file - src: https://raw.githubusercontent.com/cowboy/jquery-throttle-debounce/v1.1/jquery.ba-throttle-debounce.js - integrity: sha384-ULOy4DbAghrCqRcrTJLXOY9e4gDpWh0BeEf6xMSL0VtNudXWggcb6AmrVrl4KDAP - dest: jquery.ba-throttle-debounce.js - -moment: - type: tar - src: https://codeload.github.com/moment/moment/tar.gz/2.24.0 - integrity: sha384-2/I9rfqkN8AAgh5wOXXphuo827uV7lMmOodrCfIvqC6W6JKKiDGOwd+lE3e8R0yz - dest: - moment-2.24.0/moment.js: - moment-2.24.0/CHANGELOG.md: - moment-2.24.0/README.md: - moment-2.24.0/LICENSE: - moment-2.24.0/locale/*.js: locale - -mustache: - type: multi-file - files: - mustache.js: - src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/mustache.js - integrity: sha384-k2UYqmzoiq/qgIzZvcYBxbXQW4YdPAsXDOTkHTGb9TCZ9sjCkyT4TlaUN0wQRkql - LICENSE: - src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/LICENSE - integrity: sha384-MYVwXwula9+YkyXexOJVZ0v0DaVvG22uX57mNq5Di+7u8OH9EG9q3yuXkp1Iehiq - -oojs: - type: tar - src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz - integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw= - dest: - package/dist/oojs.jquery.js: - package/AUTHORS.txt: - package/LICENSE-MIT: - package/README.md: - -oojs-router: - type: tar - src: https://registry.npmjs.org/oojs-router/-/oojs-router-0.2.0.tgz - integrity: sha384-VngYqdQ3vTDMXbm4e4FUZCCGos7fB0Jkr9V+kBL5MElprK1h0yQZOzBNnMHtSJS/ - dest: - package/dist/oojs-router.js: - package/LICENSE: - package/AUTHORS.txt: - package/History.md: - -ooui: - type: tar - src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.31.0.tgz - integrity: sha384-kmMOvTjLZbr0Nd1iiV61KSDevnZffuY0jpr7Wjoo61HrJY53b9SacjQqebcNzTEK - dest: - # Main stuff - package/dist/oojs-ui-core.js{,.map.json}: - package/dist/oojs-ui-core-{wikimediaui,apex}.css: - package/dist/oojs-ui-widgets.js{,.map.json}: - package/dist/oojs-ui-widgets-{wikimediaui,apex}.css: - package/dist/oojs-ui-toolbars.js{,.map.json}: - package/dist/oojs-ui-toolbars-{wikimediaui,apex}.css: - package/dist/oojs-ui-windows.js{,.map.json}: - package/dist/oojs-ui-windows-{wikimediaui,apex}.css: - package/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json}: - package/dist/i18n: - package/dist/images: - # WikimediaUI theme - package/dist/themes/wikimediaui/images/icons/*.{svg,png}: themes/wikimediaui/images/icons - package/dist/themes/wikimediaui/images/indicators/*.{svg,png}: themes/wikimediaui/images/indicators - package/dist/themes/wikimediaui/images/textures/*.{gif,svg}: themes/wikimediaui/images/textures - package/src/themes/wikimediaui/*.json: themes/wikimediaui - package/dist/wikimedia-ui-base.less: - # Apex theme (icons, indicators, and textures) - package/src/themes/apex/*.json: themes/apex - # Misc stuff - package/dist/AUTHORS.txt: - package/dist/History.md: - package/dist/LICENSE-MIT: - package/dist/README.md: - -qunitjs: - type: multi-file - # Integrity from link modals at https://code.jquery.com/qunit/ - files: - qunit.js: - src: http://code.jquery.com/qunit/qunit-2.9.1.js - integrity: sha256-eNccBdxd8zReziWcVjEsPeyJDi3LKMYnzMXyDv8bzsU= - qunit.css: - src: https://code.jquery.com/qunit/qunit-2.9.1.css - integrity: sha256-SSS7o92V7wzcIFg3qnJL9mc4msePaT4klbxtuSGvVVo= - -sinonjs: - type: file - src: https://sinonjs.org/releases/sinon-1.17.7.js - integrity: sha384-wR63Jwy75KqwBfzCmXd6gYws6uj3qV/XMAybzXrkEYGYG3AQ58ZWwr1fVpkHa5e8 - dest: sinon.js diff --git a/maintenance/resources/manageForeignResources.php b/maintenance/resources/manageForeignResources.php deleted file mode 100644 index 6de82c0912..0000000000 --- a/maintenance/resources/manageForeignResources.php +++ /dev/null @@ -1,80 +0,0 @@ -addDescription( <<addArg( 'action', 'One of "update", "verify" or "make-sri"', true ); - $this->addArg( 'module', 'Name of a single module (Default: all)', false ); - $this->addOption( 'verbose', 'Be verbose', false, false, 'v' ); - } - - /** - * @return bool - * @throws Exception - */ - public function execute() { - global $IP; - $frm = new ForeignResourceManager( - __DIR__ . '/foreign-resources.yaml', - "{$IP}/resources/lib", - function ( $text ) { - $this->output( $text ); - }, - function ( $text ) { - $this->error( $text ); - }, - function ( $text ) { - if ( $this->hasOption( 'verbose' ) ) { - $this->output( $text ); - } - } - ); - - $action = $this->getArg( 0 ); - $module = $this->getArg( 1, 'all' ); - return $frm->run( $action, $module ); - } -} - -$maintClass = ManageForeignResources::class; -require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/storage/checkStorage.php b/maintenance/storage/checkStorage.php index a95789d366..26d4e79787 100644 --- a/maintenance/storage/checkStorage.php +++ b/maintenance/storage/checkStorage.php @@ -86,7 +86,6 @@ class CheckStorage { foreach ( $res as $row ) { $this->oldIdMap[$row->rev_id] = $row->rev_text_id; } - $dbr->freeResult( $res ); if ( !count( $this->oldIdMap ) ) { continue; @@ -147,7 +146,6 @@ class CheckStorage { $this->addError( 'unfixable', "Error: invalid flags field \"$flags\"", $id ); } } - $dbr->freeResult( $res ); // Output errors for any missing text rows foreach ( $missingTextRows as $oldId => $revId ) { @@ -187,7 +185,6 @@ class CheckStorage { $externalNormalBlobs[$cluster][$id][] = $row->old_id; } } - $dbr->freeResult( $res ); } // Check external concat blobs for the right header @@ -210,7 +207,6 @@ class CheckStorage { foreach ( $res as $row ) { unset( $xBlobIds[$row->blob_id] ); } - $extDb->freeResult( $res ); // Print errors for missing blobs rows foreach ( $xBlobIds as $blobId => $oldId ) { $this->addError( @@ -279,7 +275,6 @@ class CheckStorage { $this->addError( 'unfixable', "Error: unrecognised object class \"$className\"", $oldId ); } } - $dbr->freeResult( $res ); } // Check local concat blob validity @@ -333,7 +328,6 @@ class CheckStorage { unset( $concatBlobs[$row->old_id] ); } - $dbr->freeResult( $res ); } // Check targets of unresolved stubs @@ -421,7 +415,6 @@ class CheckStorage { } unset( $oldIds[$row->blob_id] ); } - $extDb->freeResult( $res ); // Print errors for missing blobs rows foreach ( $oldIds as $blobId => $oldIds2 ) { diff --git a/resources/Resources.php b/resources/Resources.php index 5e5f3087f3..ec44a8316a 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -294,7 +294,7 @@ return [ ], 'jquery.spinner' => [ 'scripts' => 'resources/src/jquery.spinner/spinner.js', - 'styles' => 'resources/src/jquery.spinner/spinner.css', + 'styles' => 'resources/src/jquery.spinner/spinner.less', 'targets' => [ 'desktop', 'mobile' ], ], 'jquery.jStorage' => [ @@ -1453,7 +1453,9 @@ return [ 'styles' => 'resources/src/mediawiki.action/mediawiki.action.history.css', ], 'mediawiki.action.history.styles' => [ - 'styles' => 'resources/src/mediawiki.action/mediawiki.action.history.styles.css', + 'skinStyles' => [ + 'default' => 'resources/src/mediawiki.action/mediawiki.action.history.styles.css', + ], ], 'mediawiki.action.view.dblClickEdit' => [ 'scripts' => 'resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js', @@ -1763,19 +1765,6 @@ return [ 'removedwatchtext-talk', ], ], - 'mediawiki.page.rollback' => [ - 'scripts' => 'resources/src/mediawiki.page.rollback.js', - 'dependencies' => [ - 'mediawiki.api', - 'mediawiki.notify', - 'mediawiki.util', - 'jquery.spinner', - ], - 'messages' => [ - 'rollbackfailed', - 'actioncomplete', - ], - ], 'mediawiki.page.rollback.confirmation' => [ 'scripts' => 'resources/src/mediawiki.rollback.confirmation.js', 'dependencies' => [ @@ -2023,8 +2012,8 @@ return [ 'parentheses-end', 'pipe-separator' ], - 'styles' => [ - 'resources/src/mediawiki.interface.helpers.styles.less', + 'skinStyles' => [ + 'default' => 'resources/src/mediawiki.interface.helpers.styles.less', ], 'targets' => [ 'desktop', 'mobile' @@ -2146,7 +2135,10 @@ return [ ], 'mediawiki.special.changeslist' => [ 'styles' => [ - 'resources/src/mediawiki.special.changeslist.less', + 'resources/src/mediawiki.special.changeslist/changeslist.less' + ], + 'skinStyles' => [ + 'default' => 'resources/src/mediawiki.special.changeslist/default.less', ], 'targets' => [ 'desktop', 'mobile' ], ], diff --git a/resources/lib/foreign-resources.yaml b/resources/lib/foreign-resources.yaml new file mode 100644 index 0000000000..8dc01ff27c --- /dev/null +++ b/resources/lib/foreign-resources.yaml @@ -0,0 +1,259 @@ +# ## Format of this file +# +# The top-level keys in this file correspond with directories under resources/lib/. +# These in turn are registered as module bundles in Resources.php. +# +# ## How to install an foreign resource +# +# 1. Add or update the url(s) for the upstream module to this YAML file. +# +# Look at other modules for examples. To install a module from npm, +# we use the tarball distribution from npmjs.org. This is the same as what +# the npm CLI uses. For example, to install jquery-client@9.2.0, use: +# . +# +# 2. If the upstream maintainers publish an integrity hash, set that as well. +# Otherwise, use manageForeignResources.php to compute the integrity hash. +# +# Run `php manageForeignResources.php make-sri "my module name"` +# +# This will download the specified file(s) and print their integrity hashes, +# already formatted in YAML, ready for copying to this file. +# +# 3. Last but not least, decide where files go. +# +# If you specified a direct url to JavaScript or CSS file, this step is +# optional. See the corresponding documentation section below for more +# information and examples for "dest" keys. Once you've set any "dest" keys, +# run `php manageForeignResources.php update "my module name"`. +# +# ## Package formats +# +# Each top-level key must use one of these types: +# +# - `file`: For a plain file. +# - `multi-file`: For multiple plain files. +# - `tar`: For a tarball archive (may be compressed). +# +# ### The "file" type +# +# * `src`: Full URL to the remote resource. +# * `integrity`: Cryptographic hash (integrity metadata format per ). +# * `dest`: [optional] The file name to use in the module directory. Default: Basename of URL. +# +# For example, the following would produce resources/lib/mymodule/x.js: +# +# mymodule: +# type: file +# src: https://mymodule.example/1.2.3/x.js +# integrity: sha384-Je+NE+saisQuoi +# +# ### The "multi-file" type +# +# * `files`: An object mapping destination paths to `src` and `integrity` keys. +# +# For example: +# +# mymodule: +# type: multi-file +# files: +# x.js: +# src: https://mymodule.example/1.2.3/x.js +# integrity: sha384-Je+NE+saisQuoi +# x.css: +# src: https://mymodule.example/1.2.3/x.css +# integrity: sha384-Je+NE+saisQuoi +# +# ### The "tar" type +# +# * `src`: Full URL to the remote resource. +# * `integrity`: Cryptographic hash (integrity metadata format per ). +# * `dest`: [optional] The default is to extract all files from the package. +# To only extract some of the files or directories, use "dest" to specify +# files, directories, and/or glob patterns. You can use a site like https://unpkg.com/ +# to easily inspect an npm package, like . +# +# For example: +# +# mymodule: +# type: tar +# src: https://registry.npmjs.org/jquery-client/-/jquery-client-9.2.0.tgz +# integrity: sha384-Je+NE+saisQuoi +# dest: +# package/dist/x.js: +# package/dist/i18n: +# package/dist/style/*.css: +# +# The would extract the "x.js" file, the "i18n" directory (recursive), +# and any "*.css" files from the "style" directory. +# + +CLDRPluralRuleParser: + type: file + src: https://raw.githubusercontent.com/santhoshtr/CLDRPluralRuleParser/0dda851/src/CLDRPluralRuleParser.js + integrity: sha384-M4taeYYG2+9Ob1/La16iO+zlRRmBV5lBR3xUKkQT6kfkJ0aLbCi6yc0RYI1BDzdh + +easy-deflate: + type: multi-file + files: + deflate.js: + src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/deflate.js + integrity: sha384-sHnZLDSWMUhA2w9ygkzCK8YFvoh/fQKY6lXMbvmrYzjuNURiLB0DZFCDNMpGyZ77 + easydeflate.js: + src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/easydeflate.js + integrity: sha384-EwPfP2RMkDPa1HkzQsXgzTsy1KEjcIzQPA1HDS/JPHjvEMvVUsCxWwm1oXql/jk2 + inflate.js: + src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/inflate.js + integrity: sha384-hMg44Hw424mUYvmzKl0JT4J8UU/1YYhTiGRtR0YX/MXNLK9qWTK0d62FBCDGxmxw + README.md: + src: https://raw.githubusercontent.com/edg2s/Easy-Deflate/7a6056e5302f6f385ff2efa60afda45b4ad81e51/README.md + integrity: sha384-6kwcfCLivvqXBZy2ATyya+mTVWLk3eaQyBdC6tbpBtkygnBrM2SNkq3jz/l7IkvP + +html5shiv: + type: file + src: https://raw.githubusercontent.com/aFarkas/html5shiv/3.7.3/src/html5shiv.js + integrity: sha384-RPXhaTf22QktT8KTwZ6bUz/C+7CnccaIw5W/y/t0FW5WSDGj3wc3YtRIJC0w47in + +jquery: + type: file + src: https://code.jquery.com/jquery-3.3.1.js + # Integrity from link modals https://code.jquery.com/jquery/ + integrity: sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60= + dest: jquery.js + +jquery.client: + type: tar + src: https://registry.npmjs.org/jquery-client/-/jquery-client-2.0.2.tgz + integrity: sha256-8c8nBbBykHEMc4I7ksdKJvvw/P7WkaC2X46RTPdz/pw= + dest: + package/AUTHORS.txt: + package/jquery.client.js: + package/LICENSE-MIT: + package/README.md: + +jquery.cookie: + type: multi-file + files: + jquery.cookie.js: + src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/jquery.cookie.js + integrity: sha384-Xxq63E9KDgzUJ6WPNPqVeOtRIwZyx6y9DzEwY2u6LYKSnWrjSoGtWSKmTindYBf2 + MIT-LICENSE.txt: + src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/MIT-LICENSE.txt + integrity: sha384-zYsGf3KJ7S0AhOICjcoh0kkn7aGZlzYUXXX5xz8dwR9KjLMM+/JPR2g/jVOGGeId + CHANGELOG.md: + src: https://raw.githubusercontent.com/carhartl/jquery-cookie/v1.3.1/CHANGELOG.md + integrity: sha384-SQOHhLc7PHxHDQpGE/zv9XfXKL0A7OBu8kuyVDnHVp+zSoWyRw4xUJ+LSm5ql4kS + +jquery.form: + type: file + src: https://raw.githubusercontent.com/jquery-form/form/ff80d9ddf4/jquery.form.js + integrity: sha384-h4G2CrcSbixzMvrrK259cNBYaL/vS1D4+KdUN9NJDzQnTU1bQ6Avluget+Id13M7 + +jquery.fullscreen: + type: file + src: https://raw.githubusercontent.com/theopolisme/jquery-fullscreen/v2.1.0/jquery.fullscreen.js + integrity: sha384-G4KPs2d99tgcsyUnJ3eeZ1r2hEKDwZfc4+/xowL/LIemq2VVwEE8HpVAWt4WYNLR + +jquery.hoverIntent: + type: file + src: https://raw.githubusercontent.com/briancherne/jquery-hoverIntent/823603fdac/jquery.hoverIntent.js + integrity: sha384-lca0haN0hqFGGh2aYUhtAgX9dhVHfQnTADH4svDeM6gcXnL7aFGeAi1NYwipDMyS + +jquery.jStorage: + type: file + src: https://raw.githubusercontent.com/andris9/jStorage/v0.4.12/jstorage.js + integrity: sha384-geMeN8k803kPp6cqRL4VNfuSM1L8DcbKRk0St/KHJzxgpX9S0y9FA6HxA/JgucrJ + +jquery.throttle-debounce: + type: file + src: https://raw.githubusercontent.com/cowboy/jquery-throttle-debounce/v1.1/jquery.ba-throttle-debounce.js + integrity: sha384-ULOy4DbAghrCqRcrTJLXOY9e4gDpWh0BeEf6xMSL0VtNudXWggcb6AmrVrl4KDAP + +moment: + type: tar + src: https://codeload.github.com/moment/moment/tar.gz/2.24.0 + integrity: sha384-2/I9rfqkN8AAgh5wOXXphuo827uV7lMmOodrCfIvqC6W6JKKiDGOwd+lE3e8R0yz + dest: + moment-2.24.0/moment.js: + moment-2.24.0/CHANGELOG.md: + moment-2.24.0/README.md: + moment-2.24.0/LICENSE: + moment-2.24.0/locale/*.js: locale + +mustache: + type: multi-file + files: + mustache.js: + src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/mustache.js + integrity: sha384-k2UYqmzoiq/qgIzZvcYBxbXQW4YdPAsXDOTkHTGb9TCZ9sjCkyT4TlaUN0wQRkql + LICENSE: + src: https://raw.githubusercontent.com/janl/mustache.js/v1.0.0/LICENSE + integrity: sha384-MYVwXwula9+YkyXexOJVZ0v0DaVvG22uX57mNq5Di+7u8OH9EG9q3yuXkp1Iehiq + +oojs: + type: tar + src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz + integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw= + dest: + package/dist/oojs.jquery.js: + package/AUTHORS.txt: + package/LICENSE-MIT: + package/README.md: + +oojs-router: + type: tar + src: https://registry.npmjs.org/oojs-router/-/oojs-router-0.2.0.tgz + integrity: sha384-VngYqdQ3vTDMXbm4e4FUZCCGos7fB0Jkr9V+kBL5MElprK1h0yQZOzBNnMHtSJS/ + dest: + package/dist/oojs-router.js: + package/LICENSE: + package/AUTHORS.txt: + package/History.md: + +ooui: + type: tar + src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.31.2.tgz + integrity: sha384-nwfvB5KB4o+jB6NdmyyJLFTW0KzWvyKKPFT5fQvnfraNu6OTBmRKUw/JsB4PQRS/ + dest: + # Main stuff + package/dist/oojs-ui-core.js{,.map.json}: + package/dist/oojs-ui-core-{wikimediaui,apex}.css: + package/dist/oojs-ui-widgets.js{,.map.json}: + package/dist/oojs-ui-widgets-{wikimediaui,apex}.css: + package/dist/oojs-ui-toolbars.js{,.map.json}: + package/dist/oojs-ui-toolbars-{wikimediaui,apex}.css: + package/dist/oojs-ui-windows.js{,.map.json}: + package/dist/oojs-ui-windows-{wikimediaui,apex}.css: + package/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json}: + package/dist/i18n: + package/dist/images: + # WikimediaUI theme + package/dist/themes/wikimediaui/images/icons/*.{svg,png}: themes/wikimediaui/images/icons + package/dist/themes/wikimediaui/images/indicators/*.{svg,png}: themes/wikimediaui/images/indicators + package/dist/themes/wikimediaui/images/textures/*.{gif,svg}: themes/wikimediaui/images/textures + package/dist/themes/wikimediaui/*.json: themes/wikimediaui + package/dist/wikimedia-ui-base.less: + # Apex theme (icons, indicators, and textures) + package/dist/themes/apex/*.json: themes/apex + # Misc stuff + package/dist/AUTHORS.txt: + package/dist/History.md: + package/dist/LICENSE-MIT: + package/dist/README.md: + +qunitjs: + type: multi-file + # Integrity from link modals at https://code.jquery.com/qunit/ + files: + qunit.js: + src: http://code.jquery.com/qunit/qunit-2.9.1.js + integrity: sha256-eNccBdxd8zReziWcVjEsPeyJDi3LKMYnzMXyDv8bzsU= + qunit.css: + src: https://code.jquery.com/qunit/qunit-2.9.1.css + integrity: sha256-SSS7o92V7wzcIFg3qnJL9mc4msePaT4klbxtuSGvVVo= + +sinonjs: + type: file + src: https://sinonjs.org/releases/sinon-1.17.7.js + integrity: sha384-wR63Jwy75KqwBfzCmXd6gYws6uj3qV/XMAybzXrkEYGYG3AQ58ZWwr1fVpkHa5e8 + dest: sinon.js diff --git a/resources/lib/ooui/History.md b/resources/lib/ooui/History.md index 9ade84c228..9fbb3662e2 100644 --- a/resources/lib/ooui/History.md +++ b/resources/lib/ooui/History.md @@ -1,4 +1,63 @@ # OOUI Release History +## v0.31.2 / 2019-03-26 +### Features +* CheckboxInputWidget: Add support for indeterminate state (Ed Sanders & Bartosz Dziewoński) + +### Code +* DropdownInputWidget: Fix typo in Apex border styles (Ed Sanders) +* SelectFileInputWidget: Apply IE11 scrolling fix (Ed Sanders) +* TextInputWidget: Remove proprietary vendor UI extensions (Volker E.) +* PHP: Tag: Use strict comparison for `array_search` (Ed Sanders) +* icons: Identical optimization to both newspaper-ltr… and …rtl.svg icons (Thiemo Kreuz) +* icons: Make use of the auto-closing feature in SVG ``s (Thiemo Kreuz) +* icons: Remove non-standard offset from web.svg icon (Thiemo Kreuz) +* demo: Match PHP toolbar to JS (Ed Sanders) +* build: Update package-lock.json (Ed Sanders) +* build: Upgrade js-yaml sub-dependency from 3.12.1 to 3.13.0 for DoS fix (James D. Forrester) +* packages: Massively trim down which files are in npm and composer packages (James D. Forrester) + + +## v0.31.1 / 2019-03-21 +### Deprecations +* [DEPRECATING CHANGE] core: Remove unused Date.now fallback (Timo Tijhof) +* [DEPRECATING CHANGE] textures: Deprecate 'pending.gif' (Volker E.) +* [DEPRECATING CHANGE] textures: Deprecate unused 'transparency' (Volker E.) + +### Features +* MenuTagMultiselectWidget: `hideOnChoose` should be set to false (Moriel Schottlender) +* MenuTagMultiselectWidget: `highlightOnFilter` only if not `allowArbitrary` (Moriel Schottlender) +* MenuTagMultiselectWidget: Fix highlight and scrolling to item behavior (Moriel Schottlender) +* SearchInputWidget: Use click handler for indicator (Ed Sanders) +* SelectWidget: Allow multiselect mode, add to MenuTagMultiselectWidget (Moriel Schottlender) +* SelectFileWidget: Support a button-only mode (Ed Sanders) +* SelectFileWidget: Suppress misleading browser default tooltips (Bartosz Dziewoński) +* SelectFileInputWidget: Create as a super-class of SelectFileWidget (Ed Sanders) +* SelectFileInputWidget: Allow button config to be passed (Ed Sanders) +* TagMultiselectWidget: Edit by item label, not data (Moriel Schottlender) + +### Styles +* Separate SelectFileWidget and SelectFileInputWidget styles (Ed Sanders) +* themes: Provide `background` needed for PendingElement on inputs (Volker E.) +* themes: Replace 'pending.gif' with CSS animation (Volker E.) +* icons: Manually rewrite paths of tableMove….svg icons (Thiemo Kreuz) +* icons: Recreate settings.svg icon with shorter syntax (Thiemo Kreuz) +* icons: Remove invisible parts from web.svg icon (Thiemo Kreuz) +* icons: Remove unused dotted borders from imageLayout….svg icons (Thiemo Kreuz) +* icons: Use rounded elements to optimize some SVG icons (Thiemo Kreuz) + +### Code +* MenuSectionOptionWidget: Avoid select events (Gabriel Birke) +* SelectFileInputWidget: Rewrite as an ActionFieldLayout (Ed Sanders) +* testsuitegenerator: Reduce PHP test count by 40% (Ed Sanders) +* testsuitegenerator: Reduce some code duplication (Bartosz Dziewoński) +* testsuitegenerator: Use normal methods more instead of lambdas (Bartosz Dziewoński) +* docs: Clarify some types in documentation (Bartosz Dziewoński) +* docs: Fix missing `;` and typos in documentation examples (Volker E.) +* demos: Make demo toolbar narrower (Ed Sanders) +* build: Specify library entry (Stephen Niedzielski) +* Grunt: Add a quick-build-code task for JS-only quick builds (Ed Sanders) + + ## v0.31.0 / 2019-03-13 ### Breaking changes * [BREAKING CHANGE] Remove FlaggedElement from InputWidget (Ed Sanders) diff --git a/resources/lib/ooui/i18n/fa.json b/resources/lib/ooui/i18n/fa.json index e6e44637b7..ae6171da2f 100644 --- a/resources/lib/ooui/i18n/fa.json +++ b/resources/lib/ooui/i18n/fa.json @@ -31,6 +31,7 @@ "ooui-dialog-process-dismiss": "رد", "ooui-dialog-process-retry": "دوباره امتحان کنید", "ooui-dialog-process-continue": "ادامه", + "ooui-combobox-button-label": "پایین‌رونده برای جعبهٔ پایین‌رونده", "ooui-selectfile-button-select": "یک فایل انتخاب کنید", "ooui-selectfile-not-supported": "انتخاب پرونده پشتیبانی نمی‌شود", "ooui-selectfile-placeholder": "هیچ پرونده‌ای انتخاب نشده است", diff --git a/resources/lib/ooui/oojs-ui-apex.js b/resources/lib/ooui/oojs-ui-apex.js index 8847ef2355..b94c06170a 100644 --- a/resources/lib/ooui/oojs-ui-apex.js +++ b/resources/lib/ooui/oojs-ui-apex.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.2 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:20Z + * Date: 2019-03-26T23:00:40Z */ ( function ( OO ) { diff --git a/resources/lib/ooui/oojs-ui-core-apex.css b/resources/lib/ooui/oojs-ui-core-apex.css index c298481ae9..1cc40dc182 100644 --- a/resources/lib/ooui/oojs-ui-core-apex.css +++ b/resources/lib/ooui/oojs-ui-core-apex.css @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.2 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:27Z + * Date: 2019-03-26T23:00:48Z */ .oo-ui-element-hidden { display: none !important; @@ -345,7 +345,38 @@ } .oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); + background-color: #ddd; + background-image: -webkit-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-size: 1.5625em 1.5625em; + -webkit-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + -moz-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + animation: oo-ui-pendingElement-stripes 650ms linear infinite; +} +@-webkit-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } +} +@-moz-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } +} +@keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.5625em 0; + } + 100% { + background-position: 0 0; + } } @@ -1073,7 +1104,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { padding: 0.5em; font-size: inherit; font-family: inherit; - border: #ccc; + border: 1px solid #ccc; border-radius: 3px; } .oo-ui-dropdownInputWidget option { @@ -1117,32 +1148,30 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { max-width: 50em; } .oo-ui-textInputWidget .oo-ui-inputWidget-input { + -webkit-appearance: none; display: block; width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } +.oo-ui-textInputWidget input { + -moz-appearance: textfield; +} +.oo-ui-textInputWidget input::-ms-clear { + display: none; +} .oo-ui-textInputWidget textarea { overflow: auto; } .oo-ui-textInputWidget textarea.oo-ui-textInputWidget-autosized { resize: none; } -.oo-ui-textInputWidget [type='number'] { - -moz-appearance: textfield; -} .oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button, .oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } -.oo-ui-textInputWidget [type='search'] { - -webkit-appearance: none; -} -.oo-ui-textInputWidget [type='search']::-ms-clear { - display: none; -} .oo-ui-textInputWidget [type='search']::-webkit-search-decoration, .oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button { display: none; @@ -1204,7 +1233,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { line-height: 1.275; } .oo-ui-textInputWidget .oo-ui-pendingElement-pending { - background-color: transparent; + background-color: #ddd; } .oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon, .oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator { @@ -1659,6 +1688,68 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { border-left-width: 0; } +.oo-ui-selectFileInputWidget { + width: 100%; + max-width: 50em; + margin-right: 0.5em; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button { + position: relative; + overflow: hidden; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 1; + cursor: pointer; + padding-top: 100px; +} +.oo-ui-selectFileInputWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] { + display: none; +} +.oo-ui-selectFileInputWidget-info > .oo-ui-inputWidget-input { + pointer-events: none; +} +.oo-ui-selectFileInputWidget-label { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + text-overflow: ellipsis; +} +.oo-ui-selectFileInputWidget-clearButton { + position: absolute; + z-index: 2; +} +.oo-ui-selectFileInputWidget-empty .oo-ui-selectFileInputWidget-clearButton { + display: none; +} +.oo-ui-selectFileInputWidget-empty.oo-ui-widget-enabled .oo-ui-selectFileInputWidget-label { + cursor: default; +} +.oo-ui-selectFileInputWidget:last-child { + margin-right: 0; +} +.oo-ui-selectFileInputWidget-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + left: 0.5em; + right: 2.175em; + line-height: 2.3em; + margin: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + .oo-ui-defaultOverlay { position: absolute; top: 0; diff --git a/resources/lib/ooui/oojs-ui-core-wikimediaui.css b/resources/lib/ooui/oojs-ui-core-wikimediaui.css index 6766494240..335a48c43f 100644 --- a/resources/lib/ooui/oojs-ui-core-wikimediaui.css +++ b/resources/lib/ooui/oojs-ui-core-wikimediaui.css @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.2 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-14T00:52:27Z + * Date: 2019-03-26T23:00:48Z */ .oo-ui-element-hidden { display: none !important; @@ -462,7 +462,38 @@ } .oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); + background-color: #eaecf0; + background-image: -webkit-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-image: linear-gradient(135deg, #fff 25%, transparent 25%, transparent 50%, #fff 50%, #fff 75%, transparent 75%, transparent); + background-size: 1.42857143em 1.42857143em; + -webkit-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + -moz-animation: oo-ui-pendingElement-stripes 650ms linear infinite; + animation: oo-ui-pendingElement-stripes 650ms linear infinite; +} +@-webkit-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } +} +@-moz-keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } +} +@keyframes oo-ui-pendingElement-stripes { + 0% { + background-position: -1.42857143em 0; + } + 100% { + background-position: 0 0; + } } @@ -1220,6 +1251,20 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { .oo-ui-checkboxInputWidget [type='checkbox']:checked + span { background-size: 1em 1em; } +.oo-ui-checkboxInputWidget [type='checkbox']:indeterminate + span { + background-image: none; + background-color: #36c; + border-color: #36c; +} +.oo-ui-checkboxInputWidget [type='checkbox']:indeterminate + span:before { + content: ' '; + position: absolute; + top: 0.57142857em; + bottom: 0.57142857em; + left: 0.21428571em; + right: 0.21428571em; + background-color: #fff; +} .oo-ui-checkboxInputWidget [type='checkbox']:disabled + span { background-color: #c8ccd1; border-color: #c8ccd1; @@ -1454,32 +1499,30 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { max-width: 50em; } .oo-ui-textInputWidget .oo-ui-inputWidget-input { + -webkit-appearance: none; display: block; width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } +.oo-ui-textInputWidget input { + -moz-appearance: textfield; +} +.oo-ui-textInputWidget input::-ms-clear { + display: none; +} .oo-ui-textInputWidget textarea { overflow: auto; } .oo-ui-textInputWidget textarea.oo-ui-textInputWidget-autosized { resize: none; } -.oo-ui-textInputWidget [type='number'] { - -moz-appearance: textfield; -} .oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button, .oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } -.oo-ui-textInputWidget [type='search'] { - -webkit-appearance: none; -} -.oo-ui-textInputWidget [type='search']::-ms-clear { - display: none; -} .oo-ui-textInputWidget [type='search']::-webkit-search-decoration, .oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button { display: none; @@ -1522,7 +1565,6 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { user-select: none; } .oo-ui-textInputWidget .oo-ui-inputWidget-input { - -webkit-appearance: none; background-color: #fff; color: #000; margin: 0; @@ -1540,7 +1582,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { line-height: 1.286; } .oo-ui-textInputWidget .oo-ui-pendingElement-pending { - background-color: transparent; + background-color: #eaecf0; } .oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-inputWidget-input { padding-left: 2.64285714em; @@ -2083,6 +2125,70 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { opacity: 1; } +.oo-ui-selectFileInputWidget { + width: 100%; + max-width: 50em; + margin-right: 0.5em; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button { + position: relative; + overflow: hidden; +} +.oo-ui-selectFileInputWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 1; + cursor: pointer; + padding-top: 100px; +} +.oo-ui-selectFileInputWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] { + display: none; +} +.oo-ui-selectFileInputWidget-info > .oo-ui-inputWidget-input { + pointer-events: none; +} +.oo-ui-selectFileInputWidget-label { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + text-overflow: ellipsis; +} +.oo-ui-selectFileInputWidget-clearButton { + position: absolute; + z-index: 2; +} +.oo-ui-selectFileInputWidget-empty .oo-ui-selectFileInputWidget-clearButton { + display: none; +} +.oo-ui-selectFileInputWidget-empty.oo-ui-widget-enabled .oo-ui-selectFileInputWidget-label { + cursor: default; +} +.oo-ui-selectFileInputWidget:last-child { + margin-right: 0; +} +.oo-ui-selectFileInputWidget-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + right: 2.85714286em; + padding-top: 0.57142857em; + padding-left: 0.57142857em; + padding-bottom: 0.57142857em; + white-space: nowrap; +} +.oo-ui-selectFileInputWidget.oo-ui-labelElement .oo-ui-selectFileInputWidget-label { + line-height: 1; +} + .oo-ui-defaultOverlay { position: absolute; top: 0; diff --git a/resources/lib/ooui/oojs-ui-core.js b/resources/lib/ooui/oojs-ui-core.js index 3ca6632b79..b2b7bfe0fb 100644 --- a/resources/lib/ooui/oojs-ui-core.js +++ b/resources/lib/ooui/oojs-ui-core.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.2 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-20T23:07:02Z + * Date: 2019-03-26T23:00:40Z */ ( function ( OO ) { @@ -295,7 +295,7 @@ OO.ui.throttle = function ( func, wait ) { previous = 0, run = function () { timeout = null; - previous = OO.ui.now(); + previous = Date.now(); func.apply( context, args ); }; return function () { @@ -304,7 +304,7 @@ OO.ui.throttle = function ( func, wait ) { // period. If it's less, run the function immediately. If it's more, // set a timeout for the remaining time -- but don't replace an // existing timeout, since that'd indefinitely prolong the wait. - var remaining = wait - ( OO.ui.now() - previous ); + var remaining = wait - ( Date.now() - previous ); context = this; args = arguments; if ( remaining <= 0 ) { @@ -323,10 +323,12 @@ OO.ui.throttle = function ( func, wait ) { /** * A (possibly faster) way to get the current timestamp as an integer. * + * @deprecated Since 0.31.1; use `Date.now()` instead. * @return {number} Current timestamp, in milliseconds since the Unix epoch */ -OO.ui.now = Date.now || function () { - return new Date().getTime(); +OO.ui.now = function () { + OO.ui.warnDeprecation( 'OO.ui.now() is deprecated, use Date.now() instead' ); + return Date.now(); }; /** @@ -2118,7 +2120,7 @@ OO.ui.mixin.TabIndexedElement.prototype.getInputId = function () { OO.ui.mixin.TabIndexedElement.prototype.isLabelableNode = function ( $node ) { var labelableTags = [ 'button', 'meter', 'output', 'progress', 'select', 'textarea' ], - tagName = $node.prop( 'tagName' ).toLowerCase(); + tagName = ( $node.prop( 'tagName' ) || '' ).toLowerCase(); if ( tagName === 'input' && $node.attr( 'type' ) !== 'hidden' ) { return true; @@ -6420,6 +6422,7 @@ OO.ui.OptionWidget.prototype.getMatchText = function () { * Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See * the [OOUI documentation on MediaWiki] [2] for examples. * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options + * @cfg {boolean} [multiselect] Allow for multiple selections */ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Configuration initialization @@ -6436,6 +6439,7 @@ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Properties this.pressed = false; this.selecting = null; + this.multiselect = !!config.multiselect; this.onDocumentMouseUpHandler = this.onDocumentMouseUp.bind( this ); this.onDocumentMouseMoveHandler = this.onDocumentMouseMove.bind( this ); this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); @@ -6496,13 +6500,16 @@ OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget ); * A `select` event is emitted when the selection is modified programmatically with the #selectItem * method. * - * @param {OO.ui.OptionWidget|null} item Selected item + * @param {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} items Currently selected items */ /** * @event choose + * * A `choose` event is emitted when an item is chosen with the #chooseItem method. + * * @param {OO.ui.OptionWidget} item Chosen item + * @param {boolean} selected Item is selected */ /** @@ -6699,12 +6706,13 @@ OO.ui.SelectWidget.prototype.onMouseLeave = function () { OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { var nextItem, handled = false, - currentItem = this.findHighlightedItem() || this.findSelectedItem(); + currentItem = this.findHighlightedItem(), + firstItem = this.getItems()[ 0 ]; if ( !this.isDisabled() && this.isVisible() ) { switch ( e.keyCode ) { case OO.ui.Keys.ENTER: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { // Was only highlighted, now let's select it. No-op if already selected. this.chooseItem( currentItem ); handled = true; @@ -6713,18 +6721,18 @@ OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { case OO.ui.Keys.UP: case OO.ui.Keys.LEFT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, -1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, -1 ) : firstItem; handled = true; break; case OO.ui.Keys.DOWN: case OO.ui.Keys.RIGHT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, 1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, 1 ) : firstItem; handled = true; break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { currentItem.setHighlighted( false ); } this.unbindDocumentKeyDownListener(); @@ -6944,20 +6952,36 @@ OO.ui.SelectWidget.prototype.findTargetItem = function ( e ) { return $option.data( 'oo-ui-optionWidget' ) || null; }; +/** + * Find all selected items, if there are any. If the widget allows for multiselect + * it will return an array of selected options. If the widget doesn't allow for + * multiselect, it will return the selected option or null if no item is selected. + * + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected + */ +OO.ui.SelectWidget.prototype.findSelectedItems = function () { + var selected = this.items.filter( function ( item ) { + return item.isSelected(); + } ); + + return this.multiselect ? + selected : + selected[ 0 ] || null; +}; + /** * Find selected item. * - * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected */ OO.ui.SelectWidget.prototype.findSelectedItem = function () { - var i, len; - - for ( i = 0, len = this.items.length; i < len; i++ ) { - if ( this.items[ i ].isSelected() ) { - return this.items[ i ]; - } - } - return null; + return this.findSelectedItems(); }; /** @@ -7104,6 +7128,30 @@ OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) { return this.selectItem( itemFromData ); }; +/** + * Programmatically unselect an option by its reference. If the widget + * allows for multiple selections, there may be other items still selected; + * otherwise, no items will be selected. + * If no item is given, all selected items will be unselected. + * + * @param {OO.ui.OptionWidget} [item] Item to unselect + * @fires select + * @chainable + * @return {OO.ui.Widget} The widget, for chaining + */ +OO.ui.SelectWidget.prototype.unselectItem = function ( item ) { + if ( item ) { + item.setSelected( false ); + } else { + this.items.forEach( function ( item ) { + item.setSelected( false ); + } ); + } + + this.emit( 'select', this.findSelectedItems() ); + return this; +}; + /** * Programmatically select an option by its reference. If the `item` parameter is omitted, * all options will be deselected. @@ -7117,14 +7165,20 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { var i, len, selected, changed = false; - for ( i = 0, len = this.items.length; i < len; i++ ) { - selected = this.items[ i ] === item; - if ( this.items[ i ].isSelected() !== selected ) { - this.items[ i ].setSelected( selected ); - changed = true; + if ( this.multiselect && item ) { + // Select the item directly + item.setSelected( true ); + } else { + for ( i = 0, len = this.items.length; i < len; i++ ) { + selected = this.items[ i ] === item; + if ( this.items[ i ].isSelected() !== selected ) { + this.items[ i ].setSelected( selected ); + changed = true; + } } } if ( changed ) { + // TODO: When should a non-highlightable element be selected? if ( item && !item.constructor.static.highlightable ) { if ( item ) { this.$focusOwner.attr( 'aria-activedescendant', item.getElementId() ); @@ -7132,7 +7186,7 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { this.$focusOwner.removeAttr( 'aria-activedescendant' ); } } - this.emit( 'select', item ); + this.emit( 'select', this.findSelectedItems() ); } return this; @@ -7185,8 +7239,13 @@ OO.ui.SelectWidget.prototype.pressItem = function ( item ) { */ OO.ui.SelectWidget.prototype.chooseItem = function ( item ) { if ( item ) { - this.selectItem( item ); - this.emit( 'choose', item ); + if ( this.multiselect && item.isSelected() ) { + this.unselectItem( item ); + } else { + this.selectItem( item ); + } + + this.emit( 'choose', item, item.isSelected() ); } return this; @@ -7655,7 +7714,7 @@ OO.ui.MenuSelectWidget.prototype.onDocumentKeyDown = function ( e ) { break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem ) { + if ( currentItem && !this.multiselect ) { currentItem.setHighlighted( false ); } this.toggle( false ); @@ -7712,10 +7771,6 @@ OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () { section.toggle( showAll || !sectionEmpty ); } - if ( anyVisible && this.items.length && !exactMatch ) { - this.scrollItemIntoView( this.items[ 0 ] ); - } - if ( !anyVisible ) { this.highlightItem( null ); } @@ -7872,7 +7927,7 @@ OO.ui.MenuSelectWidget.prototype.clearItems = function () { * @inheritdoc */ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { - var change, originalHeight, flippedHeight; + var change, originalHeight, flippedHeight, selectedItem; visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length; change = visible !== this.isVisible(); @@ -7937,9 +7992,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { this.$focusOwner.attr( 'aria-expanded', 'true' ); - if ( this.findSelectedItem() ) { - this.$focusOwner.attr( 'aria-activedescendant', this.findSelectedItem().getElementId() ); - this.findSelectedItem().scrollElementIntoView( { duration: 0 } ); + selectedItem = this.findSelectedItem(); + if ( !this.multiselect && selectedItem ) { + // TODO: Verify if this is even needed; This is already done on highlight changes + // in SelectWidget#highlightItem, so we should just need to highlight the item we need to + // highlight here and not bother with attr or checking selections. + this.$focusOwner.attr( 'aria-activedescendant', selectedItem.getElementId() ); + selectedItem.scrollElementIntoView( { duration: 0 } ); } // Auto-hide @@ -7962,6 +8021,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { return this; }; +/** + * Scroll to the top of the menu + */ +OO.ui.MenuSelectWidget.prototype.scrollToTop = function () { + this.$element.scrollTop( 0 ); +}; + /** * DropdownWidgets are not menus themselves, rather they contain a menu of options created with * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that @@ -9401,6 +9467,7 @@ OO.ui.ButtonInputWidget.prototype.getInputId = function () { * @param {Object} [config] Configuration options * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is * not selected. + * @cfg {boolean} [indeterminate=false] Whether the checkbox is in the indeterminate state. */ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { // Configuration initialization @@ -9421,12 +9488,24 @@ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { // Required for pretty styling in WikimediaUI theme .append( this.checkIcon.$element ); this.setSelected( config.selected !== undefined ? config.selected : false ); + this.setIndeterminate( config.indeterminate !== undefined ? config.indeterminate : false ); }; /* Setup */ OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget ); +/* Events */ + +/** + * @event change + * + * A change event is emitted when the state of the input changes. + * + * @param {boolean} selected + * @param {boolean} indeterminate + */ + /* Static Properties */ /** @@ -9465,6 +9544,7 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () { // Allow the stack to clear so the value will be updated setTimeout( function () { widget.setSelected( widget.$input.prop( 'checked' ) ); + widget.setIndeterminate( widget.$input.prop( 'indeterminate' ) ); } ); } }; @@ -9472,16 +9552,20 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () { /** * Set selection state of this checkbox. * - * @param {boolean} state `true` for selected + * @param {boolean} state Selected state + * @param {boolean} internal Used for internal calls to suppress events * @chainable - * @return {OO.ui.Widget} The widget, for chaining + * @return {OO.ui.CheckboxInputWidget} The widget, for chaining */ -OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) { +OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state, internal ) { state = !!state; if ( this.selected !== state ) { this.selected = state; this.$input.prop( 'checked', this.selected ); - this.emit( 'change', this.selected ); + if ( !internal ) { + this.setIndeterminate( false, true ); + this.emit( 'change', this.selected, this.indeterminate ); + } } // The first time that the selection state is set (probably while constructing the widget), // remember it in defaultSelected. This property can be later used to check whether @@ -9508,6 +9592,42 @@ OO.ui.CheckboxInputWidget.prototype.isSelected = function () { return this.selected; }; +/** + * Set indeterminate state of this checkbox. + * + * @param {boolean} state Indeterminate state + * @param {boolean} internal Used for internal calls to suppress events + * @chainable + * @return {OO.ui.CheckboxInputWidget} The widget, for chaining + */ +OO.ui.CheckboxInputWidget.prototype.setIndeterminate = function ( state, internal ) { + state = !!state; + if ( this.indeterminate !== state ) { + this.indeterminate = state; + this.$input.prop( 'indeterminate', this.indeterminate ); + if ( !internal ) { + this.setSelected( false, true ); + this.emit( 'change', this.selected, this.indeterminate ); + } + } + return this; +}; + +/** + * Check if this checkbox is selected. + * + * @return {boolean} Checkbox is selected + */ +OO.ui.CheckboxInputWidget.prototype.isIndeterminate = function () { + // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify + // it, and we won't know unless they're kind enough to trigger a 'change' event. + var indeterminate = this.$input.prop( 'indeterminate' ); + if ( this.indeterminate !== indeterminate ) { + this.setIndeterminate( indeterminate ); + } + return this.indeterminate; +}; + /** * @inheritdoc */ @@ -10397,7 +10517,7 @@ OO.ui.CheckboxMultiselectInputWidget.prototype.focus = function () { * // A TextInputWidget. * var textInput = new OO.ui.TextInputWidget( { * value: 'Text input' - * } ) + * } ); * $( document.body ).append( textInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs @@ -11117,6 +11237,7 @@ OO.ui.SearchInputWidget = function OoUiSearchInputWidget( config ) { this.connect( this, { change: 'onChange' } ); + this.$indicator.on( 'click', this.onIndicatorClick.bind( this ) ); // Initialization this.updateSearchIndicator(); @@ -11140,9 +11261,12 @@ OO.ui.SearchInputWidget.prototype.getSaneType = function () { }; /** - * @inheritdoc + * Handle click events on the indicator + * + * @param {jQuery.Event} e Click event + * @return {boolean} */ -OO.ui.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) { +OO.ui.SearchInputWidget.prototype.onIndicatorClick = function ( e ) { if ( e.which === OO.ui.MouseButtons.LEFT ) { // Clear the text field this.setValue( '' ); @@ -11205,8 +11329,8 @@ OO.ui.SearchInputWidget.prototype.setReadOnly = function ( state ) { * // A MultilineTextInputWidget. * var multilineTextInput = new OO.ui.MultilineTextInputWidget( { * value: 'Text input on multiple lines' - * } ) - * $( 'body' ).append( multilineTextInput.$element ); + * } ); + * $( document.body ).append( multilineTextInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget * @@ -12360,21 +12484,21 @@ OO.ui.FieldsetLayout.static.tagName = 'fieldset'; * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs * * @example - * // Example of a form layout that wraps a fieldset layout + * // Example of a form layout that wraps a fieldset layout. * var input1 = new OO.ui.TextInputWidget( { - * placeholder: 'Username' - * } ); - * var input2 = new OO.ui.TextInputWidget( { - * placeholder: 'Password', - * type: 'password' - * } ); - * var submit = new OO.ui.ButtonInputWidget( { - * label: 'Submit' - * } ); + * placeholder: 'Username' + * } ), + * input2 = new OO.ui.TextInputWidget( { + * placeholder: 'Password', + * type: 'password' + * } ), + * submit = new OO.ui.ButtonInputWidget( { + * label: 'Submit' + * } ), + * fieldset = new OO.ui.FieldsetLayout( { + * label: 'A form layout' + * } ); * - * var fieldset = new OO.ui.FieldsetLayout( { - * label: 'A form layout' - * } ); * fieldset.addItems( [ * new OO.ui.FieldLayout( input1, { * label: 'Username', @@ -12565,7 +12689,7 @@ OO.ui.PanelLayout.prototype.focus = function () { * Note that inline elements, such as OO.ui.ButtonWidgets, do not need this wrapper. * * @example - * // HorizontalLayout with a text input and a label + * // HorizontalLayout with a text input and a label. * var layout = new OO.ui.HorizontalLayout( { * items: [ * new OO.ui.LabelWidget( { label: 'Label' } ), @@ -12978,6 +13102,263 @@ OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { return this; }; +/** + * SelectFileInputWidgets allow for selecting files, using . These + * widgets can be configured with {@link OO.ui.mixin.IconElement icons}, {@link + * OO.ui.mixin.IndicatorElement indicators} and {@link OO.ui.mixin.TitledElement titles}. + * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples. + * + * SelectFileInputWidgets must be used in HTML forms, as getValue only returns the filename. + * + * @example + * // A file select input widget. + * var selectFile = new OO.ui.SelectFileInputWidget(); + * $( document.body ).append( selectFile.$element ); + * + * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets + * + * @class + * @extends OO.ui.InputWidget + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types. + * @cfg {string} [placeholder] Text to display when no file is selected. + * @cfg {Object} [button] Config to pass to select file button. + * @cfg {string} [icon] Icon to show next to file info + */ +OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { + config = config || {}; + + // Construct buttons before parent method is called (calling setDisabled) + this.selectButton = new OO.ui.ButtonWidget( $.extend( { + $element: $( '